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 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