gitlab_quality-test_tooling 1.19.1 → 1.20.1
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 +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
|