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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: beeb1678e873990d65b474eeda84dd957becfa69e7868d83d9db5394aa9e71be
4
- data.tar.gz: 8a932d572750522124c0fa24950db55a1d9e5de66e476cde0f1c17a7fb16f9d0
3
+ metadata.gz: 94c648574a575a2a7d23ce07a46731500edbd858ae8196e6b50ac104d260b88e
4
+ data.tar.gz: '0468936a09caafa090ea0f91a20bb952314a7ebabd2704d35d760901ef3f8939'
5
5
  SHA512:
6
- metadata.gz: a5bf865481746bcf4e54d10e2b19b5b8ac63aefb151eb285b47566aae82e3fcf7d30ccd90603bc8a44c3fa06996f4a6bc3486575fe6aa002cf596f4e5bd0e366
7
- data.tar.gz: bc1857f7fbf20040a2c4c0e4c696b3d31410b07b664f5aedcc2a5535d8c5e1ec1e5e52b8dda6fe906b1253ba218615cfbe9cb2234c130de27f3d605e8979fcff
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.19.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.4
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
- ci_project_client = Gitlab.client(
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
- # Execute the processor
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
- def execute(spec, context) # rubocop:disable Metrics/AbcSize
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
- @file_contents = context.get_file_contents(file_path)
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
- new_content, @changed_line_no = add_blocking_metadata
28
+ mr_title = format("%{prefix} %{file}", prefix: '[E2E PROMOTE TO BLOCKING]', file: file).truncate(72, omission: '')
29
29
 
30
- return unless proceed_with_merge_request?
30
+ reviewer_id, assignee_handle = context.fetch_dri_id(product_group, devops_stage, product_section)
31
31
 
32
- branch = context.create_branch("blocking-promotion-#{SecureRandom.hex(4)}", example_name, context.ref)
32
+ gitlab_bot_user_id = context.user_id_for_username(Runtime::Env.gitlab_bot_username)
33
33
 
34
- context.commit_changes(branch, <<~COMMIT_MESSAGE, file_path, new_content)
35
- Promote end-to-end test to blocking
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 test [`#{example_name}`](https://gitlab.com/#{context.project}/-/blob/#{context.ref}/#{file_path}#L#{changed_line_no + 1})
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
- [Testcase link](#{testcase})
40
+ #{mr_spec_details_from_commits(record[:commits])}
54
41
 
55
- [Spec metrics link](#{context.single_spec_metrics_link(example_name)})
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
- MARKDOWN
65
- end
51
+ MARKDOWN
52
+ end
66
53
 
67
- context.post_note_on_merge_request(<<~MARKDOWN, merge_request.iid)
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
- MARKDOWN
58
+ MARKDOWN
72
59
 
73
- if merge_request
74
- context.add_processed_record({ file_path => changed_line_no })
75
- Runtime::Logger.info(" Created MR for promotion to blocking: #{merge_request.web_url}")
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
- merge_request
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 proceed_with_merge_request? # rubocop:disable Metrics/AbcSize
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.record_processed?(file_path, changed_line_no)
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 add_blocking_metadata # rubocop:disable Metrics/AbcSize
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
- puts "Example '#{example_name}' is already blocking"
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
- # Execute the processor
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
- def execute(spec, context) # rubocop:disable Metrics/AbcSize
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
- @file_contents = context.get_file_contents(file_path)
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
- new_content, @changed_line_no = add_quarantine_metadata
33
+ mr_title = format("%{prefix} %{file}", prefix: '[QUARANTINE]', file: file).truncate(72, omission: '')
41
34
 
42
- return unless proceed_with_merge_request?
35
+ gitlab_bot_user_id = context.user_id_for_username(Runtime::Env.gitlab_bot_username)
43
36
 
44
- branch = context.create_branch("#{issue_id}-quarantine-#{SecureRandom.hex(4)}", example_name, context.ref)
45
-
46
- context.commit_changes(branch, <<~COMMIT_MESSAGE, file_path, new_content)
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
- if merge_request
99
- context.add_processed_record({ file_path => changed_line_no })
100
- Runtime::Logger.info(" Created MR for quarantine: #{merge_request.web_url}")
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
- merge_request
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
- :issue_id, :mr_title, :failure_issue, :changed_line_no
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 proceed_with_merge_request? # rubocop:disable Metrics/AbcSize
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.record_processed?(file_path, changed_line_no)
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 existing_mrs&.any?
147
- Runtime::Logger.info(" An open MR already exists for '#{example_name}': #{existing_mrs.first['web_url']}. Will not proceed with creating MR.")
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 add_quarantine_metadata # rubocop:disable Metrics/AbcSize
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 execute
14
- raise 'method not implemented'
13
+ def post_process
14
+ raise NotImplementedError, 'Subclass must implement this method'
15
15
  end
16
16
 
17
- def post_process
18
- raise 'method not implemented'
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, :processed_records
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
- @processed_records = {}
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
- results << processor.execute(spec, self)
30
+ processor.create_commit(spec, self)
32
31
  end
33
- processor.post_process(results)
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
- # Add processed records
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 [Hash<String,Integer>] record the processed record
40
- # @option record [String] :file_path the path to the spec file
41
- # @option spec [Intenger] :changed_line_no the line number change in file_path
42
- # @return [Hash<String,Integer>] processed_records
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
- matched_lines << [line, line_index] if string_within_quotes && example_name.include?(string_within_quotes)
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] example_name the example
151
+ # @param [String] name the branch name
99
152
  # @return [Gitlab::ObjectifiedHash] the new branch
100
- def create_branch(name_prefix, example_name, ref)
101
- branch_name = [name_prefix, example_name.gsub(/\W/, '-')]
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module GitlabQuality
4
4
  module TestTooling
5
- VERSION = "1.19.1"
5
+ VERSION = "1.20.1"
6
6
  end
7
7
  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.19.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-14 00:00:00.000000000 Z
11
+ date: 2024-03-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: climate_control