gitlab_quality-test_tooling 1.19.1 → 1.20.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +2 -2
- data/lib/gitlab_quality/test_tooling/gitlab_client/repository_files_client.rb +4 -3
- data/lib/gitlab_quality/test_tooling/report/generate_test_session.rb +20 -13
- data/lib/gitlab_quality/test_tooling/test_meta/processor/add_to_blocking_processor.rb +43 -50
- data/lib/gitlab_quality/test_tooling/test_meta/processor/add_to_quarantine_processor.rb +77 -83
- data/lib/gitlab_quality/test_tooling/test_meta/processor/meta_processor.rb +61 -4
- data/lib/gitlab_quality/test_tooling/test_meta/test_meta_updater.rb +71 -27
- data/lib/gitlab_quality/test_tooling/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 94c648574a575a2a7d23ce07a46731500edbd858ae8196e6b50ac104d260b88e
|
4
|
+
data.tar.gz: '0468936a09caafa090ea0f91a20bb952314a7ebabd2704d35d760901ef3f8939'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 24f5968a3038b68a6dc44ec7d9873ad759a73454ac8fed7bca965e0c139cb2e25324ad4a3aea841e6f302b141fa5799abae4eebdb2d58d2b04fa3f09b099b1f8
|
7
|
+
data.tar.gz: 2d943ff87a109e8951e2889cb6ed34321fcde844d311494de5a9f6808dee4fe80014b81d42765a244fa8338ddf2599852bb46824f50996a0db709ffd664bb5ea
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
gitlab_quality-test_tooling (1.
|
4
|
+
gitlab_quality-test_tooling (1.20.1)
|
5
5
|
activesupport (>= 6.1, < 7.2)
|
6
6
|
amatch (~> 0.4.1)
|
7
7
|
gitlab (~> 4.19)
|
@@ -328,4 +328,4 @@ DEPENDENCIES
|
|
328
328
|
webmock (= 3.7.0)
|
329
329
|
|
330
330
|
BUNDLED WITH
|
331
|
-
2.5.
|
331
|
+
2.5.6
|
@@ -4,17 +4,18 @@ module GitlabQuality
|
|
4
4
|
module TestTooling
|
5
5
|
module GitlabClient
|
6
6
|
class RepositoryFilesClient < GitlabClient
|
7
|
-
attr_reader :file_path
|
7
|
+
attr_reader :file_path, :ref
|
8
8
|
|
9
|
-
def initialize(file_path:, **kwargs)
|
9
|
+
def initialize(file_path:, ref:, **kwargs)
|
10
10
|
@file_path = file_path
|
11
|
+
@ref = ref
|
11
12
|
|
12
13
|
super
|
13
14
|
end
|
14
15
|
|
15
16
|
def file_contents
|
16
17
|
handle_gitlab_client_exceptions do
|
17
|
-
client.file_contents(project, file_path.gsub(%r{^/}, ""))
|
18
|
+
client.file_contents(project, file_path.gsub(%r{^/}, ""), ref)
|
18
19
|
end
|
19
20
|
end
|
20
21
|
end
|
@@ -83,20 +83,10 @@ module GitlabQuality
|
|
83
83
|
end
|
84
84
|
|
85
85
|
def generate_failed_jobs_listing
|
86
|
-
failed_jobs =
|
87
|
-
|
88
|
-
|
89
|
-
endpoint: Runtime::Env.ci_api_v4_url,
|
90
|
-
private_token: ci_project_token)
|
91
|
-
|
92
|
-
gitlab.handle_gitlab_client_exceptions do
|
93
|
-
failed_jobs = ci_project_client.pipeline_jobs(
|
94
|
-
Runtime::Env.ci_project_id,
|
95
|
-
Runtime::Env.ci_pipeline_id,
|
96
|
-
scope: 'failed')
|
97
|
-
end
|
86
|
+
failed_jobs = fetch_pipeline_failed_jobs
|
87
|
+
listings = failed_jobs.filter_map do |job|
|
88
|
+
next if pipeline_stages.any? && !pipeline_stages.include?(job.stage)
|
98
89
|
|
99
|
-
listings = failed_jobs.map do |job|
|
100
90
|
allowed_to_fail = ' (allowed to fail)' if job.allow_failure
|
101
91
|
|
102
92
|
"* [#{job.name}](#{job.web_url})#{allowed_to_fail}"
|
@@ -275,6 +265,23 @@ module GitlabQuality
|
|
275
265
|
* https://dashboards.quality.gitlab.net/d/tR_SmBDVk/main-runs?orgId=1&refresh=1m&var-run_type=#{Runtime::Env.qa_run_type}
|
276
266
|
MARKDOWN
|
277
267
|
end
|
268
|
+
|
269
|
+
def fetch_pipeline_failed_jobs
|
270
|
+
failed_jobs = []
|
271
|
+
|
272
|
+
ci_project_client = Gitlab.client(
|
273
|
+
endpoint: Runtime::Env.ci_api_v4_url,
|
274
|
+
private_token: ci_project_token)
|
275
|
+
|
276
|
+
gitlab.handle_gitlab_client_exceptions do
|
277
|
+
failed_jobs = ci_project_client.pipeline_jobs(
|
278
|
+
Runtime::Env.ci_project_id,
|
279
|
+
Runtime::Env.ci_pipeline_id,
|
280
|
+
scope: 'failed')
|
281
|
+
end
|
282
|
+
|
283
|
+
failed_jobs
|
284
|
+
end
|
278
285
|
end
|
279
286
|
end
|
280
287
|
end
|
@@ -6,53 +6,40 @@ module GitlabQuality
|
|
6
6
|
module Processor
|
7
7
|
class AddToBlockingProcessor < MetaProcessor
|
8
8
|
BLOCKING_METADATA = ", :blocking%{suffix}"
|
9
|
+
BRANCH_PREFIX = 'blocking-promotion'
|
9
10
|
|
10
11
|
class << self
|
11
|
-
#
|
12
|
+
# Creates the merge requests for promoting E2E tests to :blocking
|
12
13
|
#
|
13
|
-
# @param [Hash] spec the spec to update
|
14
14
|
# @param [TestMetaUpdater] context instance of TestMetaUpdater
|
15
|
-
|
15
|
+
# @return [Array<Gitlab::ObjectifiedHash>]
|
16
|
+
def create_merge_requests(context) # rubocop:disable Metrics/AbcSize
|
16
17
|
@context = context
|
17
|
-
@existing_mrs = nil
|
18
|
-
@file_path = spec["file_path"]
|
19
|
-
testcase = spec["testcase"]
|
20
|
-
product_section = spec["section"]
|
21
|
-
devops_stage = spec["stage"]
|
22
|
-
product_group = spec["product_group"]
|
23
|
-
@example_name = spec["name"]
|
24
|
-
@mr_title = format("%{prefix} %{example_name}", prefix: '[PROMOTE TO BLOCKING]', example_name: example_name).truncate(72, omission: '')
|
25
18
|
|
26
|
-
|
19
|
+
created_merge_requests = []
|
20
|
+
context.processed_commits.each_value do |record|
|
21
|
+
branch = record[:branch]
|
22
|
+
first_spec = record[:commits].values.first
|
23
|
+
devops_stage = first_spec["stage"]
|
24
|
+
product_section = first_spec["section"]
|
25
|
+
product_group = first_spec["product_group"]
|
26
|
+
file = first_spec["file"]
|
27
27
|
|
28
|
-
|
28
|
+
mr_title = format("%{prefix} %{file}", prefix: '[E2E PROMOTE TO BLOCKING]', file: file).truncate(72, omission: '')
|
29
29
|
|
30
|
-
|
30
|
+
reviewer_id, assignee_handle = context.fetch_dri_id(product_group, devops_stage, product_section)
|
31
31
|
|
32
|
-
|
32
|
+
gitlab_bot_user_id = context.user_id_for_username(Runtime::Env.gitlab_bot_username)
|
33
33
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
#{"Promote to blocking: #{example_name}".truncate(72)}
|
38
|
-
COMMIT_MESSAGE
|
39
|
-
|
40
|
-
reviewer_id, assignee_handle = context.fetch_dri_id(product_group, devops_stage, product_section)
|
41
|
-
|
42
|
-
gitlab_bot_user_id = context.user_id_for_username(Runtime::Env.gitlab_bot_username)
|
43
|
-
|
44
|
-
merge_request = context.create_merge_request(mr_title, branch, gitlab_bot_user_id, [reviewer_id]) do
|
45
|
-
<<~MARKDOWN
|
34
|
+
merge_request = context.create_merge_request(mr_title, branch, gitlab_bot_user_id, [reviewer_id]) do
|
35
|
+
<<~MARKDOWN
|
46
36
|
## What does this MR do?
|
47
37
|
|
48
|
-
Promotes the
|
49
|
-
to the blocking bucket
|
50
|
-
|
51
|
-
This test was identified in the reliable e2e test report: #{context.report_issue}
|
38
|
+
Promotes the following e2e tests to the blocking bucket:
|
52
39
|
|
53
|
-
[
|
40
|
+
#{mr_spec_details_from_commits(record[:commits])}
|
54
41
|
|
55
|
-
|
42
|
+
This MR was created based on data from reliable e2e test report: #{context.report_issue}
|
56
43
|
|
57
44
|
/label ~"Quality" ~"QA" ~"type::maintenance"
|
58
45
|
/label ~"devops::#{devops_stage}"
|
@@ -61,26 +48,27 @@ module GitlabQuality
|
|
61
48
|
<div align="center">
|
62
49
|
(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})
|
63
50
|
</div>
|
64
|
-
|
65
|
-
|
51
|
+
MARKDOWN
|
52
|
+
end
|
66
53
|
|
67
|
-
|
54
|
+
context.post_note_on_merge_request(<<~MARKDOWN, merge_request.iid)
|
68
55
|
@#{assignee_handle} Please review this MR, approve and assign it to a maintainer.
|
69
56
|
|
70
57
|
If you think this MR should not be merged, please close it and add a note of the reason to the blocking report: #{context.report_issue}
|
71
|
-
|
58
|
+
MARKDOWN
|
72
59
|
|
73
|
-
|
74
|
-
|
75
|
-
|
60
|
+
if merge_request
|
61
|
+
Runtime::Logger.info(" Created MR for promotion to blocking: #{merge_request.web_url}")
|
62
|
+
created_merge_requests << merge_request
|
63
|
+
end
|
76
64
|
end
|
77
65
|
|
78
|
-
|
66
|
+
created_merge_requests
|
79
67
|
end
|
80
68
|
|
81
69
|
# Performs post processing. Takes a list of MRs and posts them in a note on report_issue
|
82
70
|
#
|
83
|
-
# @param [Gitlab::ObjectifiedHash] merge_requests
|
71
|
+
# @param [Array<Gitlab::ObjectifiedHash>] merge_requests
|
84
72
|
def post_process(merge_requests)
|
85
73
|
web_urls = merge_requests.compact.map { |mr| "- #{mr.web_url}\n" }.join
|
86
74
|
|
@@ -95,34 +83,39 @@ module GitlabQuality
|
|
95
83
|
|
96
84
|
private
|
97
85
|
|
98
|
-
attr_reader :context, :file_path, :file_contents, :example_name, :mr_title, :changed_line_no
|
86
|
+
attr_reader :context, :file_path, :file, :file_contents, :example_name, :mr_title, :changed_line_no
|
99
87
|
|
100
88
|
# Checks if there is already an MR open
|
101
89
|
#
|
102
90
|
# @return [Boolean]
|
103
|
-
def
|
91
|
+
def proceed_with_commit?
|
104
92
|
if changed_line_no.negative?
|
105
93
|
Runtime::Logger.info(" No lines were changed in #{file_path}. Will not proceed with creating MR.")
|
106
94
|
return false
|
107
|
-
elsif context.
|
95
|
+
elsif context.commit_processed?(file_path, changed_line_no)
|
108
96
|
Runtime::Logger.info(" Record already processed for #{file_path}:#{changed_line_no}. Will not proceed with creating MR.")
|
109
97
|
return false
|
110
|
-
elsif existing_mrs&.any?
|
111
|
-
Runtime::Logger.info(" An open MR already exists for '#{example_name}': #{existing_mrs.first['web_url']}. Will not proceed with creating MR.")
|
112
|
-
return false
|
113
98
|
end
|
114
99
|
|
115
100
|
true
|
116
101
|
end
|
117
102
|
|
103
|
+
def commit_message
|
104
|
+
<<~COMMIT_MESSAGE
|
105
|
+
Promote end-to-end test to blocking
|
106
|
+
|
107
|
+
#{"Promote to blocking: #{example_name}".truncate(72)}
|
108
|
+
COMMIT_MESSAGE
|
109
|
+
end
|
110
|
+
|
118
111
|
# Add blocking metadata to the file content and replace it
|
119
112
|
#
|
120
113
|
# @return [Array<String, Integer>] first value holds the new content, the second value holds the line number of the test
|
121
|
-
def
|
114
|
+
def add_metadata # rubocop:disable Metrics/AbcSize
|
122
115
|
matched_lines = context.find_example_match_lines(file_contents, example_name)
|
123
116
|
|
124
117
|
if matched_lines.any? { |line| line[0].include?(':blocking') }
|
125
|
-
|
118
|
+
Runtime::Logger.info("Example '#{example_name}' is already blocking")
|
126
119
|
return [file_contents, -1]
|
127
120
|
end
|
128
121
|
|
@@ -12,95 +12,39 @@ module GitlabQuality
|
|
12
12
|
%{indentation} type: %{quarantine_type}
|
13
13
|
%{indentation}}%{suffix}
|
14
14
|
META
|
15
|
+
BRANCH_PREFIX = 'quarantine'
|
15
16
|
|
16
17
|
class << self
|
17
|
-
#
|
18
|
+
# Creates the merge requests to quarantine E2E tests
|
18
19
|
#
|
19
|
-
# @param [Hash<String,String>] spec the spec to update
|
20
|
-
# @option spec [String] :file_path the path to the spec file
|
21
|
-
# @option spec [String] :stage the stage of the test
|
22
|
-
# @option spec [String] :failure_issue the issue url of the failure
|
23
|
-
# @option spec [String] :name the name of the example
|
24
20
|
# @param [TestMetaUpdater] context instance of TestMetaUpdater
|
25
|
-
|
21
|
+
# @return [Array<Gitlab::ObjectifiedHash>]
|
22
|
+
def create_merge_requests(context) # rubocop:disable Metrics/AbcSize
|
26
23
|
@context = context
|
27
|
-
@existing_mrs = nil
|
28
|
-
@file_path = spec["file_path"]
|
29
|
-
testcase = spec["testcase"]
|
30
|
-
devops_stage = spec["stage"]
|
31
|
-
product_group = spec["product_group"]
|
32
|
-
@failure_issue_url = spec["failure_issue"]
|
33
|
-
@example_name = spec["name"]
|
34
|
-
@issue_id = failure_issue_url.split('/').last # split url segment, last segment of path is the issue id
|
35
|
-
@mr_title = format("%{prefix} %{example_name}", prefix: '[QUARANTINE]', example_name: example_name).truncate(72, omission: '')
|
36
|
-
@failure_issue = context.fetch_issue(iid: issue_id)
|
37
24
|
|
38
|
-
|
25
|
+
created_merge_requests = []
|
26
|
+
context.processed_commits.each_value do |record|
|
27
|
+
branch = record[:branch]
|
28
|
+
first_spec = record[:commits].values.first
|
29
|
+
devops_stage = first_spec["stage"]
|
30
|
+
product_group = first_spec["product_group"]
|
31
|
+
file = first_spec["file"]
|
39
32
|
|
40
|
-
|
33
|
+
mr_title = format("%{prefix} %{file}", prefix: '[QUARANTINE]', file: file).truncate(72, omission: '')
|
41
34
|
|
42
|
-
|
35
|
+
gitlab_bot_user_id = context.user_id_for_username(Runtime::Env.gitlab_bot_username)
|
43
36
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
Quarantine end-to-end test
|
48
|
-
|
49
|
-
#{"Quarantine #{example_name}".truncate(72)}
|
50
|
-
COMMIT_MESSAGE
|
51
|
-
|
52
|
-
gitlab_bot_user_id = context.user_id_for_username(Runtime::Env.gitlab_bot_username)
|
53
|
-
|
54
|
-
merge_request = context.create_merge_request(mr_title, branch, gitlab_bot_user_id) do
|
55
|
-
<<~MARKDOWN
|
56
|
-
## What does this MR do?
|
57
|
-
|
58
|
-
Quarantines the test [`#{example_name}`](https://gitlab.com/#{context.project}/-/blob/#{context.ref}/#{file_path}#L#{changed_line_no + 1})
|
59
|
-
|
60
|
-
This test was identified in the reliable e2e test report: #{context.report_issue}
|
61
|
-
|
62
|
-
[Testcase link](#{testcase})
|
63
|
-
|
64
|
-
[Spec metrics link](#{context.single_spec_metrics_link(example_name)})
|
65
|
-
|
66
|
-
### E2E Test Failure issue(s)
|
67
|
-
|
68
|
-
#{failure_issue_url}
|
69
|
-
|
70
|
-
### Check-list
|
71
|
-
|
72
|
-
- [ ] General code guidelines check-list
|
73
|
-
- [ ] [Code review guidelines](https://docs.gitlab.com/ee/development/code_review.html)
|
74
|
-
- [ ] [Style guides](https://docs.gitlab.com/ee/development/contributing/style_guides.html)
|
75
|
-
- [ ] Quarantine test check-list
|
76
|
-
- [ ] Follow the [Quarantining Tests guide](https://about.gitlab.com/handbook/engineering/infrastructure/test-platform/debugging-qa-test-failures/#quarantining-tests).
|
77
|
-
- [ ] Confirm the test has a [`quarantine:` tag with the specified quarantine type](https://about.gitlab.com/handbook/engineering/infrastructure/test-platform/debugging-qa-test-failures/#quarantined-test-types).
|
78
|
-
- [ ] Note if the test should be [quarantined for a specific environment](https://docs.gitlab.com/ee/development/testing_guide/end_to_end/execution_context_selection.html#quarantine-a-test-for-a-specific-environment).
|
79
|
-
- [ ] (Optionally) In case of an emergency (e.g. blocked deployments), consider adding labels to pick into auto-deploy (~"Pick into auto-deploy" ~"priority::1" ~"severity::1").
|
80
|
-
- [ ] To ensure a faster turnaround, ask in the `#quality_maintainers` Slack channel for someone to review and merge the merge request, rather than assigning it directly.
|
81
|
-
|
82
|
-
<!-- Base labels. -->
|
83
|
-
/label ~"Quality" ~"QA" ~"type::maintenance" ~"maintenance::pipelines"
|
84
|
-
|
85
|
-
<!--
|
86
|
-
Choose the stage that appears in the test path, e.g. ~"devops::create" for
|
87
|
-
`qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb`.
|
88
|
-
-->
|
89
|
-
/label ~"devops::#{devops_stage}"
|
90
|
-
#{context.label_from_product_group(product_group)}
|
91
|
-
|
92
|
-
<div align="center">
|
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})
|
94
|
-
</div>
|
95
|
-
MARKDOWN
|
96
|
-
end
|
37
|
+
merge_request = context.create_merge_request(mr_title, branch, gitlab_bot_user_id) do
|
38
|
+
merge_request_description(record, devops_stage, product_group)
|
39
|
+
end
|
97
40
|
|
98
|
-
|
99
|
-
|
100
|
-
|
41
|
+
if merge_request
|
42
|
+
Runtime::Logger.info(" Created MR for quarantine: #{merge_request.web_url}")
|
43
|
+
created_merge_requests << merge_request
|
44
|
+
end
|
101
45
|
end
|
102
46
|
|
103
|
-
|
47
|
+
created_merge_requests
|
104
48
|
end
|
105
49
|
|
106
50
|
# Performs post processing. Takes a list of MRs and posts them in a note on report_issue and Slack
|
@@ -131,30 +75,80 @@ module GitlabQuality
|
|
131
75
|
private
|
132
76
|
|
133
77
|
attr_reader :context, :file_path, :file_contents, :failure_issue_url, :example_name,
|
134
|
-
:
|
78
|
+
:mr_title, :failure_issue, :changed_line_no
|
135
79
|
|
136
80
|
# Checks if the failure issue is closed or if there is already an MR open
|
137
81
|
#
|
138
82
|
# @return [Boolean]
|
139
|
-
def
|
83
|
+
def proceed_with_commit?
|
140
84
|
if context.issue_is_closed?(failure_issue)
|
141
85
|
Runtime::Logger.info(" Failure issue '#{failure_issue_url}' is closed. Will not proceed with creating MR.")
|
142
86
|
return false
|
143
|
-
elsif context.
|
87
|
+
elsif context.commit_processed?(file_path, changed_line_no)
|
144
88
|
Runtime::Logger.info(" Record already processed for #{file_path}:#{changed_line_no}. Will not proceed with creating MR.")
|
145
89
|
return false
|
146
|
-
elsif
|
147
|
-
Runtime::Logger.info("
|
90
|
+
elsif failure_is_related_to_test_environment?
|
91
|
+
Runtime::Logger.info(" Failure issue '#{failure_issue_url}' is environment related. Will not proceed with creating MR.")
|
148
92
|
return false
|
149
93
|
end
|
150
94
|
|
151
95
|
true
|
152
96
|
end
|
153
97
|
|
98
|
+
def failure_is_related_to_test_environment?
|
99
|
+
context.issue_scoped_label(failure_issue, 'failure')&.split('::')&.last == 'test-environment'
|
100
|
+
end
|
101
|
+
|
102
|
+
def merge_request_description(record, devops_stage, product_group)
|
103
|
+
<<~MARKDOWN
|
104
|
+
## What does this MR do?
|
105
|
+
|
106
|
+
Quarantines the following e2e tests:
|
107
|
+
|
108
|
+
#{mr_spec_details_from_commits(record[:commits])}
|
109
|
+
|
110
|
+
This MR was created based on data from reliable e2e test report: #{context.report_issue}#{' '}
|
111
|
+
|
112
|
+
### Check-list
|
113
|
+
|
114
|
+
- [ ] General code guidelines check-list
|
115
|
+
- [ ] [Code review guidelines](https://docs.gitlab.com/ee/development/code_review.html)
|
116
|
+
- [ ] [Style guides](https://docs.gitlab.com/ee/development/contributing/style_guides.html)
|
117
|
+
- [ ] Quarantine test check-list
|
118
|
+
- [ ] Follow the [Quarantining Tests guide](https://about.gitlab.com/handbook/engineering/infrastructure/test-platform/debugging-qa-test-failures/#quarantining-tests).
|
119
|
+
- [ ] Confirm the test has a [`quarantine:` tag with the specified quarantine type](https://about.gitlab.com/handbook/engineering/infrastructure/test-platform/debugging-qa-test-failures/#quarantined-test-types).
|
120
|
+
- [ ] Note if the test should be [quarantined for a specific environment](https://docs.gitlab.com/ee/development/testing_guide/end_to_end/execution_context_selection.html#quarantine-a-test-for-a-specific-environment).
|
121
|
+
- [ ] (Optionally) In case of an emergency (e.g. blocked deployments), consider adding labels to pick into auto-deploy (~"Pick into auto-deploy" ~"priority::1" ~"severity::1").
|
122
|
+
- [ ] To ensure a faster turnaround, ask in the `#quality_maintainers` Slack channel for someone to review and merge the merge request, rather than assigning it directly.
|
123
|
+
|
124
|
+
<!-- Base labels. -->
|
125
|
+
/label ~"Quality" ~"QA" ~"type::maintenance" ~"maintenance::pipelines"
|
126
|
+
|
127
|
+
<!--
|
128
|
+
Choose the stage that appears in the test path, e.g. ~"devops::create" for
|
129
|
+
`qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb`.
|
130
|
+
-->
|
131
|
+
/label ~"devops::#{devops_stage}"
|
132
|
+
#{context.label_from_product_group(product_group)}
|
133
|
+
|
134
|
+
<div align="center">
|
135
|
+
(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})
|
136
|
+
</div>
|
137
|
+
MARKDOWN
|
138
|
+
end
|
139
|
+
|
140
|
+
def commit_message
|
141
|
+
<<~COMMIT_MESSAGE
|
142
|
+
Quarantine end-to-end test
|
143
|
+
|
144
|
+
#{"Quarantine #{example_name}".truncate(72)}
|
145
|
+
COMMIT_MESSAGE
|
146
|
+
end
|
147
|
+
|
154
148
|
# Add quarantine metadata to the file content and replace it
|
155
149
|
#
|
156
150
|
# @return [Array<String, Integer>] first value holds the new content, the second value holds the line number of the test
|
157
|
-
def
|
151
|
+
def add_metadata # rubocop:disable Metrics/AbcSize
|
158
152
|
matched_lines = context.find_example_match_lines(file_contents, example_name)
|
159
153
|
|
160
154
|
context.update_matched_line(matched_lines.last, file_contents.dup) do |line|
|
@@ -10,12 +10,12 @@ module GitlabQuality
|
|
10
10
|
class << self
|
11
11
|
DESCRIPTION_REGEX = /('.*?')|(".*?")/
|
12
12
|
|
13
|
-
def
|
14
|
-
raise '
|
13
|
+
def post_process
|
14
|
+
raise NotImplementedError, 'Subclass must implement this method'
|
15
15
|
end
|
16
16
|
|
17
|
-
def
|
18
|
-
raise '
|
17
|
+
def create_merge_requests
|
18
|
+
raise NotImplementedError, 'Subclass must implement this method'
|
19
19
|
end
|
20
20
|
|
21
21
|
private_class_method :new
|
@@ -36,6 +36,63 @@ module GitlabQuality
|
|
36
36
|
description_start_index = line.index(DESCRIPTION_REGEX)
|
37
37
|
description_start_index + description_length
|
38
38
|
end
|
39
|
+
|
40
|
+
# List specs in markdown with details such as link to code, testcase, metrics and failure issue
|
41
|
+
#
|
42
|
+
# @param [Hash<String,Hash>] commits The commits hash to use for spec details
|
43
|
+
# @return String
|
44
|
+
def mr_spec_details_from_commits(commits)
|
45
|
+
commits.each_with_index.map do |(changed_line_number, spec), index|
|
46
|
+
<<~MARKDOWN
|
47
|
+
#{index + 1}. [`#{spec['name']}`](https://gitlab.com/#{context.project}/-/blob/#{context.ref}/#{spec['file_path']}#L#{changed_line_number.to_i + 1})
|
48
|
+
| [Testcase](#{spec['testcase']}) | [Spec metrics](#{context.single_spec_metrics_link(spec['name'])})
|
49
|
+
#{failure_issue_text(spec)}
|
50
|
+
MARKDOWN
|
51
|
+
end.join("\n")
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns a string in markdown of failure issue and its link
|
55
|
+
#
|
56
|
+
# @param [Hash] spec the spec for failure issue
|
57
|
+
# @return [String]
|
58
|
+
def failure_issue_text(spec)
|
59
|
+
spec['failure_issue'].empty? ? '' : "| [Failure issue](#{spec['failure_issue']})"
|
60
|
+
end
|
61
|
+
|
62
|
+
# Creates a commit depending on the context provided and adds it to a Hash of created commits
|
63
|
+
#
|
64
|
+
# @param [Hash] spec the spec to update
|
65
|
+
# @param [TestMetaUpdater] context instance of TestMetaUpdater
|
66
|
+
def create_commit(spec, context) # rubocop:disable Metrics/AbcSize
|
67
|
+
@context = context
|
68
|
+
@file_path = spec["file_path"]
|
69
|
+
@file = spec["file"]
|
70
|
+
@example_name = spec["name"]
|
71
|
+
@failure_issue_url = spec["failure_issue"]
|
72
|
+
|
73
|
+
issue_id = failure_issue_url&.split('/')&.last # split url segment, last segment of path is the issue id
|
74
|
+
existing_branch = context.branch_for_file_path(file_path)
|
75
|
+
|
76
|
+
@file_contents = context.get_file_contents(file_path: file_path,
|
77
|
+
branch: existing_branch && existing_branch['name'])
|
78
|
+
|
79
|
+
@failure_issue = context.fetch_issue(iid: issue_id) if issue_id
|
80
|
+
|
81
|
+
new_content, @changed_line_no = add_metadata
|
82
|
+
|
83
|
+
return unless proceed_with_commit?
|
84
|
+
|
85
|
+
branch = existing_branch ||
|
86
|
+
context.create_branch("#{self::BRANCH_PREFIX}-#{SecureRandom.hex(4)}", @file, context.ref)
|
87
|
+
|
88
|
+
context.commit_changes(branch, commit_message, file_path, new_content)
|
89
|
+
|
90
|
+
context.add_processed_commit(file_path, changed_line_no, branch, spec)
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
attr_reader :failure_issue_url, :failure_issue
|
39
96
|
end
|
40
97
|
end
|
41
98
|
end
|
@@ -8,7 +8,7 @@ module GitlabQuality
|
|
8
8
|
class TestMetaUpdater
|
9
9
|
include TestTooling::Concerns::FindSetDri
|
10
10
|
|
11
|
-
attr_reader :project, :ref, :report_issue, :
|
11
|
+
attr_reader :project, :ref, :report_issue, :processed_commits
|
12
12
|
|
13
13
|
TEST_PLATFORM_MAINTAINERS_SLACK_CHANNEL_ID = 'C0437FV9KBN' # test-platform-maintainers
|
14
14
|
|
@@ -19,37 +19,84 @@ module GitlabQuality
|
|
19
19
|
@ref = ref
|
20
20
|
@dry_run = dry_run
|
21
21
|
@processor = processor
|
22
|
-
@
|
22
|
+
@processed_commits = {}
|
23
23
|
end
|
24
24
|
|
25
25
|
def invoke!
|
26
26
|
JSON.parse(File.read(specs_file)).tap do |contents|
|
27
27
|
@report_issue = contents['report_issue']
|
28
28
|
|
29
|
-
results = []
|
30
29
|
contents['specs'].each do |spec|
|
31
|
-
|
30
|
+
processor.create_commit(spec, self)
|
32
31
|
end
|
33
|
-
|
32
|
+
|
33
|
+
created_merge_requests = processor.create_merge_requests(self)
|
34
|
+
|
35
|
+
processor.post_process(created_merge_requests)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Add processed commits.
|
40
|
+
#
|
41
|
+
# processed_commits has the following form. Note that each key in the :commits hash
|
42
|
+
# is the changed line number and the value is the spec changed.
|
43
|
+
#
|
44
|
+
# {
|
45
|
+
# "/file/path/for/spec_1" =>
|
46
|
+
# { :commits =>
|
47
|
+
# {
|
48
|
+
# "34" => {"stage"=> "create", "product_group" => "source_code".. },
|
49
|
+
# "38" => {"stage"=> "create", "product_group" => "source_code".. }
|
50
|
+
# },
|
51
|
+
# :branch => #<Gitlab::ObjectifiedHash>
|
52
|
+
# },
|
53
|
+
# "/file/path/for/spec_2" =>
|
54
|
+
# { :commits =>
|
55
|
+
# {
|
56
|
+
# "34" => {"stage"=> "create", "product_group" => "source_code".. },
|
57
|
+
# "38" => {"stage"=> "create", "product_group" => "source_code".. }
|
58
|
+
# },
|
59
|
+
# :branch => #<Gitlab::ObjectifiedHash>
|
60
|
+
# },
|
61
|
+
# }
|
62
|
+
#
|
63
|
+
# @param [<String>] file_path the file path to the spec
|
64
|
+
# @param [<Integer>] changed_line_no the changed line number for the commit
|
65
|
+
# @param [<Gitlab::ObjectifiedHash>] branch the branch for the commit
|
66
|
+
# @param [<Hash>] spec spec details hash
|
67
|
+
# @return [Hash<String,Hash>] processed_commits
|
68
|
+
def add_processed_commit(file_path, changed_line_no, branch, spec)
|
69
|
+
if processed_commits[file_path].nil?
|
70
|
+
processed_commits[file_path] = { commits: { changed_line_no.to_s => spec }, branch: branch }
|
71
|
+
elsif processed_commits[file_path][:commits][changed_line_no.to_s].nil?
|
72
|
+
processed_commits[file_path][:commits].merge!({ changed_line_no.to_s => spec })
|
34
73
|
end
|
35
74
|
end
|
36
75
|
|
37
|
-
#
|
76
|
+
# Checks if changes have already been made to given file_path and line number
|
77
|
+
#
|
78
|
+
# @param [String] file_path path to the file
|
79
|
+
# @param [Integer] changed_line_no updated line number
|
80
|
+
# @return [Boolean]
|
81
|
+
def commit_processed?(file_path, changed_line_no)
|
82
|
+
processed_commits[file_path] && processed_commits[file_path][:commits][changed_line_no.to_s]
|
83
|
+
end
|
84
|
+
|
85
|
+
# Returns the branch for the given file_path
|
38
86
|
#
|
39
|
-
# @param [
|
40
|
-
# @
|
41
|
-
|
42
|
-
|
43
|
-
def add_processed_record(record)
|
44
|
-
@processed_records.merge!(record)
|
87
|
+
# @param [String] file_path path to the file
|
88
|
+
# @return [<Gitlab::ObjectifiedHash>]
|
89
|
+
def branch_for_file_path(file_path)
|
90
|
+
processed_commits[file_path] && processed_commits[file_path][:branch]
|
45
91
|
end
|
46
92
|
|
47
93
|
# Fetch contents of file from the repository
|
48
94
|
#
|
49
95
|
# @param [String] file_path path to the file
|
96
|
+
# @param [String] branch branch ref
|
50
97
|
# @return [String] contents of the file
|
51
|
-
def get_file_contents(file_path)
|
52
|
-
repository_files = GitlabClient::RepositoryFilesClient.new(token: token, project: project, file_path: file_path)
|
98
|
+
def get_file_contents(file_path:, branch:)
|
99
|
+
repository_files = GitlabClient::RepositoryFilesClient.new(token: token, project: project, file_path: file_path, ref: branch || ref)
|
53
100
|
repository_files.file_contents
|
54
101
|
end
|
55
102
|
|
@@ -62,11 +109,17 @@ module GitlabQuality
|
|
62
109
|
lines = content.split("\n")
|
63
110
|
|
64
111
|
matched_lines = []
|
112
|
+
example_name_for_parsing = example_name.dup
|
65
113
|
|
66
114
|
lines.each_with_index do |line, line_index|
|
67
115
|
string_within_quotes = spec_desc_string_within_quotes(line)
|
68
116
|
|
69
|
-
|
117
|
+
regex = /^\s?#{Regexp.escape(string_within_quotes)}/ if string_within_quotes
|
118
|
+
|
119
|
+
if !example_name_for_parsing.empty? && regex && example_name_for_parsing.match(regex)
|
120
|
+
example_name_for_parsing.sub!(regex, '')
|
121
|
+
matched_lines << [line, line_index]
|
122
|
+
end
|
70
123
|
rescue StandardError => e
|
71
124
|
puts "Error: #{e}"
|
72
125
|
end
|
@@ -95,10 +148,10 @@ module GitlabQuality
|
|
95
148
|
# Create a branch from the ref
|
96
149
|
#
|
97
150
|
# @param [String] name_prefix the prefix to attach to the branch name
|
98
|
-
# @param [String]
|
151
|
+
# @param [String] name the branch name
|
99
152
|
# @return [Gitlab::ObjectifiedHash] the new branch
|
100
|
-
def create_branch(name_prefix,
|
101
|
-
branch_name = [name_prefix,
|
153
|
+
def create_branch(name_prefix, name, ref)
|
154
|
+
branch_name = [name_prefix, name.gsub(/\W/, '-')]
|
102
155
|
@branches_client ||= (dry_run ? GitlabClient::BranchesDryClient : GitlabClient::BranchesClient).new(token: token, project: project)
|
103
156
|
@branches_client.create(branch_name.join('-'), ref)
|
104
157
|
end
|
@@ -243,15 +296,6 @@ module GitlabQuality
|
|
243
296
|
merge_request_client.find(options: { search: title, in: 'title', state: 'opened' })
|
244
297
|
end
|
245
298
|
|
246
|
-
# Checks if changes has already been made to given file and line number
|
247
|
-
#
|
248
|
-
# @param [String] file_path path to the file
|
249
|
-
# @param [Integer] changed_line_no updated line number
|
250
|
-
# @return [Boolean]
|
251
|
-
def record_processed?(file_path, changed_line_no)
|
252
|
-
processed_records[file_path] && processed_records[file_path] == changed_line_no
|
253
|
-
end
|
254
|
-
|
255
299
|
# Infers product group label from the provided product group
|
256
300
|
#
|
257
301
|
# @param [String] product_group product group
|
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.20.1
|
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-03-
|
11
|
+
date: 2024-03-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: climate_control
|