gitlab-qa 10.3.0 → 12.2.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.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -1
  3. data/.gitlab/changelog_config.yml +13 -0
  4. data/.gitlab/merge_request_templates/Release.md +13 -34
  5. data/.gitlab-ci.yml +3 -1
  6. data/.rubocop.yml +13 -2
  7. data/.rubocop_todo.yml +57 -97
  8. data/Dangerfile +1 -5
  9. data/Gemfile.lock +47 -39
  10. data/README.md +1 -2
  11. data/docs/release_process.md +1 -1
  12. data/docs/running_against_remote_grid.md +42 -3
  13. data/docs/what_tests_can_be_run.md +5 -2
  14. data/gitlab-qa.gemspec +5 -3
  15. data/lib/gitlab/qa/component/base.rb +9 -9
  16. data/lib/gitlab/qa/component/gitaly.rb +7 -5
  17. data/lib/gitlab/qa/component/gitaly_cluster.rb +15 -9
  18. data/lib/gitlab/qa/component/gitlab.rb +48 -21
  19. data/lib/gitlab/qa/component/mail_hog.rb +1 -0
  20. data/lib/gitlab/qa/component/praefect.rb +41 -31
  21. data/lib/gitlab/qa/component/selenoid.rb +14 -7
  22. data/lib/gitlab/qa/component/specs.rb +11 -5
  23. data/lib/gitlab/qa/component/staging.rb +4 -4
  24. data/lib/gitlab/qa/component/telegraf.rb +2 -1
  25. data/lib/gitlab/qa/docker/engine.rb +6 -3
  26. data/lib/gitlab/qa/docker/volumes.rb +1 -1
  27. data/lib/gitlab/qa/release.rb +4 -4
  28. data/lib/gitlab/qa/runner.rb +10 -3
  29. data/lib/gitlab/qa/runtime/env.rb +47 -62
  30. data/lib/gitlab/qa/runtime/logger.rb +1 -1
  31. data/lib/gitlab/qa/runtime/omnibus_configuration.rb +1 -0
  32. data/lib/gitlab/qa/runtime/omnibus_configurations/decomposition_single_db.rb +1 -2
  33. data/lib/gitlab/qa/runtime/omnibus_configurations/object_storage_gcs.rb +2 -1
  34. data/lib/gitlab/qa/runtime/scenario.rb +1 -5
  35. data/lib/gitlab/qa/scenario/actable.rb +4 -4
  36. data/lib/gitlab/qa/scenario/test/instance/airgapped.rb +2 -4
  37. data/lib/gitlab/qa/scenario/test/instance/deployment_base.rb +2 -1
  38. data/lib/gitlab/qa/scenario/test/integration/gitaly_cluster.rb +0 -2
  39. data/lib/gitlab/qa/scenario/test/integration/group_saml.rb +1 -1
  40. data/lib/gitlab/qa/scenario/test/integration/ldap.rb +5 -6
  41. data/lib/gitlab/qa/scenario/test/integration/mtls.rb +20 -6
  42. data/lib/gitlab/qa/scenario/test/integration/oauth.rb +13 -4
  43. data/lib/gitlab/qa/scenario/test/integration/praefect.rb +16 -10
  44. data/lib/gitlab/qa/scenario/test/integration/registry_with_cdn.rb +5 -2
  45. data/lib/gitlab/qa/scenario/test/omnibus/update_from_previous.rb +1 -1
  46. data/lib/gitlab/qa/scenario/test/omnibus/upgrade.rb +1 -3
  47. data/lib/gitlab/qa/scenario/test/sanity/version.rb +1 -1
  48. data/lib/gitlab/qa/support/config_scripts.rb +1 -1
  49. data/lib/gitlab/qa/support/gitlab_version_info.rb +30 -10
  50. data/lib/gitlab/qa/support/shell_command.rb +1 -0
  51. data/lib/gitlab/qa/test_logger.rb +2 -2
  52. data/lib/gitlab/qa/version.rb +1 -1
  53. data/lib/gitlab/qa.rb +0 -1
  54. data/support/data/admin_access_token_seed.rb +5 -1
  55. data/support/data/license_usage_seed.rb +3 -1
  56. metadata +16 -45
  57. data/bin/slack +0 -14
  58. data/exe/gitlab-qa-report +0 -10
  59. data/lib/gitlab/qa/report/base_test_results.rb +0 -39
  60. data/lib/gitlab/qa/report/find_set_dri.rb +0 -43
  61. data/lib/gitlab/qa/report/generate_test_session.rb +0 -275
  62. data/lib/gitlab/qa/report/gitlab_issue_client.rb +0 -190
  63. data/lib/gitlab/qa/report/gitlab_issue_dry_client.rb +0 -28
  64. data/lib/gitlab/qa/report/j_unit_test_results.rb +0 -27
  65. data/lib/gitlab/qa/report/json_test_results.rb +0 -29
  66. data/lib/gitlab/qa/report/prepare_stage_reports.rb +0 -86
  67. data/lib/gitlab/qa/report/relate_failure_issue.rb +0 -374
  68. data/lib/gitlab/qa/report/report_as_issue.rb +0 -176
  69. data/lib/gitlab/qa/report/report_results.rb +0 -64
  70. data/lib/gitlab/qa/report/results_in_issues.rb +0 -126
  71. data/lib/gitlab/qa/report/results_in_testcases.rb +0 -111
  72. data/lib/gitlab/qa/report/results_reporter_shared.rb +0 -70
  73. data/lib/gitlab/qa/report/summary_table.rb +0 -43
  74. data/lib/gitlab/qa/report/test_result.rb +0 -184
  75. data/lib/gitlab/qa/report/update_screenshot_path.rb +0 -63
  76. data/lib/gitlab/qa/reporter.rb +0 -131
  77. data/lib/gitlab/qa/runtime/omnibus_configurations/packages.rb +0 -17
  78. data/lib/gitlab/qa/runtime/token_finder.rb +0 -44
  79. data/lib/gitlab/qa/slack/post_to_slack.rb +0 -30
  80. data/lib/gitlab/qa/system_logs/finders/json_log_finder.rb +0 -65
  81. data/lib/gitlab/qa/system_logs/finders/rails/api_log_finder.rb +0 -21
  82. data/lib/gitlab/qa/system_logs/finders/rails/application_log_finder.rb +0 -21
  83. data/lib/gitlab/qa/system_logs/finders/rails/exception_log_finder.rb +0 -21
  84. data/lib/gitlab/qa/system_logs/finders/rails/graphql_log_finder.rb +0 -21
  85. data/lib/gitlab/qa/system_logs/log_types/log.rb +0 -38
  86. data/lib/gitlab/qa/system_logs/log_types/rails/api_log.rb +0 -34
  87. data/lib/gitlab/qa/system_logs/log_types/rails/application_log.rb +0 -27
  88. data/lib/gitlab/qa/system_logs/log_types/rails/exception_log.rb +0 -23
  89. data/lib/gitlab/qa/system_logs/log_types/rails/graphql_log.rb +0 -30
  90. data/lib/gitlab/qa/system_logs/shared_fields.rb +0 -29
  91. data/lib/gitlab/qa/system_logs/system_logs_formatter.rb +0 -65
@@ -1,275 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'erb'
4
- require 'date'
5
-
6
- module Gitlab
7
- module QA
8
- module Report
9
- class GenerateTestSession < ReportAsIssue
10
- def initialize(**kwargs)
11
- super
12
- @issue_type = 'issue'
13
- end
14
-
15
- private
16
-
17
- # rubocop:disable Metrics/AbcSize
18
- def run!
19
- puts "Generating test results in `#{files.join(',')}` as issues in project `#{project}` via the API at `#{Runtime::Env.gitlab_api_base}`."
20
-
21
- tests = Dir.glob(files).flat_map do |path|
22
- puts "Loading tests in #{path}"
23
-
24
- Report::JsonTestResults.new(path).to_a
25
- end
26
-
27
- issue = gitlab.create_issue(
28
- title: "#{Time.now.strftime('%Y-%m-%d')} Test session report | #{Runtime::Env.qa_run_type}",
29
- description: generate_description(tests),
30
- labels: ['Quality', 'QA', 'triage report', pipeline_name_label]
31
- )
32
-
33
- # Workaround for https://gitlab.com/gitlab-org/gitlab/-/issues/295493
34
- unless Runtime::Env.qa_issue_url.to_s.empty?
35
- gitlab.create_issue_note(
36
- iid: issue.iid,
37
- note: "/relate #{Runtime::Env.qa_issue_url}")
38
- end
39
-
40
- File.write('REPORT_ISSUE_URL', issue.web_url)
41
- end
42
- # rubocop:enable Metrics/AbcSize
43
-
44
- def generate_description(tests)
45
- <<~MARKDOWN.rstrip
46
- ## Session summary
47
-
48
- * Deploy version: #{Runtime::Env.deploy_version}
49
- * Deploy environment: #{Runtime::Env.deploy_environment}
50
- * Pipeline: #{Runtime::Env.pipeline_from_project_name} [#{Runtime::Env.ci_pipeline_id}](#{Runtime::Env.ci_pipeline_url})
51
- #{generate_summary(tests: tests)}
52
-
53
- #{generate_failed_jobs_listing}
54
-
55
- #{generate_stages_listing(tests)}
56
-
57
- #{generate_qa_issue_relation}
58
-
59
- #{generate_link_to_dashboard}
60
- MARKDOWN
61
- end
62
-
63
- def generate_summary(tests:, tests_by_status: nil)
64
- tests_by_status ||= tests.group_by(&:status)
65
- total = tests.size
66
- passed = tests_by_status['passed']&.size || 0
67
- failed = tests_by_status['failed']&.size || 0
68
- others = total - passed - failed
69
-
70
- <<~MARKDOWN.chomp
71
- * Total #{total} tests
72
- * Passed #{passed} tests
73
- * Failed #{failed} tests
74
- * #{others} other tests (usually skipped)
75
- MARKDOWN
76
- end
77
-
78
- def generate_failed_jobs_listing
79
- failed_jobs = []
80
-
81
- client = Gitlab.client(
82
- endpoint: Runtime::Env.ci_api_v4_url,
83
- private_token: Runtime::Env.gitlab_ci_api_token)
84
-
85
- gitlab.handle_gitlab_client_exceptions do
86
- failed_jobs = client.pipeline_jobs(
87
- Runtime::Env.ci_project_id,
88
- Runtime::Env.ci_pipeline_id,
89
- scope: 'failed')
90
- end
91
-
92
- listings = failed_jobs.map do |job|
93
- allowed_to_fail = ' (allowed to fail)' if job.allow_failure
94
-
95
- "* [#{job.name}](#{job.web_url})#{allowed_to_fail}"
96
- end.join("\n")
97
-
98
- <<~MARKDOWN.chomp if failed_jobs.any?
99
- ## Failed jobs
100
-
101
- #{listings}
102
- MARKDOWN
103
- end
104
-
105
- def generate_stages_listing(tests)
106
- generate_tests_by_stage(tests).map do |stage, tests_for_stage|
107
- tests_by_status = tests_for_stage.group_by(&:status)
108
-
109
- <<~MARKDOWN.chomp
110
- ### #{stage&.capitalize || 'Unknown'}
111
-
112
- #{generate_summary(
113
- tests: tests_for_stage, tests_by_status: tests_by_status)}
114
-
115
- #{generate_testcase_listing_by_status(
116
- tests: tests_for_stage, tests_by_status: tests_by_status)}
117
- MARKDOWN
118
- end.join("\n\n")
119
- end
120
-
121
- def generate_tests_by_stage(tests)
122
- # https://about.gitlab.com/handbook/product/product-categories/#devops-stages
123
- ordering = %w[
124
- manage
125
- plan
126
- create
127
- verify
128
- package
129
- release
130
- configure
131
- monitor
132
- secure
133
- defend
134
- growth
135
- fulfillment
136
- enablement
137
- ]
138
-
139
- tests.sort_by do |test|
140
- ordering.index(test.stage) || ordering.size
141
- end.group_by(&:stage)
142
- end
143
-
144
- def generate_testcase_listing_by_status(tests:, tests_by_status:)
145
- failed_tests = tests_by_status['failed']
146
- passed_tests = tests_by_status['passed']
147
- other_tests = tests.reject do |test|
148
- test.status == 'failed' || test.status == 'passed'
149
- end
150
-
151
- [
152
- (failed_listings(failed_tests) if failed_tests),
153
- (passed_listings(passed_tests) if passed_tests),
154
- (other_listings(other_tests) if other_tests.any?)
155
- ].compact.join("\n\n")
156
- end
157
-
158
- def failed_listings(failed_tests)
159
- generate_testcase_listing(failed_tests)
160
- end
161
-
162
- def passed_listings(passed_tests)
163
- <<~MARKDOWN.chomp
164
- <details><summary>Passed tests:</summary>
165
-
166
- #{generate_testcase_listing(passed_tests, passed: true)}
167
-
168
- </details>
169
- MARKDOWN
170
- end
171
-
172
- def other_listings(other_tests)
173
- <<~MARKDOWN.chomp
174
- <details><summary>Other tests:</summary>
175
-
176
- #{generate_testcase_listing(other_tests)}
177
-
178
- </details>
179
- MARKDOWN
180
- end
181
-
182
- def generate_testcase_listing(tests, passed: false)
183
- body = tests.group_by(&:testcase).map do |testcase, tests_with_same_testcase|
184
- tests_with_same_testcase.sort_by!(&:name)
185
- [
186
- generate_test_text(testcase, tests_with_same_testcase, passed),
187
- generate_test_job(tests_with_same_testcase),
188
- generate_test_status(tests_with_same_testcase),
189
- generate_test_actions(tests_with_same_testcase)
190
- ].join(' | ')
191
- end.join("\n")
192
-
193
- <<~MARKDOWN.chomp
194
- | Test | Job | Status | Action |
195
- | - | - | - | - |
196
- #{body}
197
- MARKDOWN
198
- end
199
-
200
- def generate_test_text(testcase, tests_with_same_testcase, passed)
201
- text = tests_with_same_testcase.map(&:name).uniq.join(', ')
202
- encoded_text = ERB::Util.url_encode(text)
203
-
204
- if testcase && !passed
205
- # Workaround for reducing system notes on testcase issues
206
- # The first regex extracts the link to the issues list page from a link to a single issue show page by removing the issue id.
207
- "[#{text}](#{testcase.match(%r{[\s\S]+\/[^\/\d]+})}?state=opened&search=#{encoded_text})"
208
- else
209
- text
210
- end
211
- end
212
-
213
- def generate_test_job(tests_with_same_testcase)
214
- tests_with_same_testcase.map do |test|
215
- ci_job_id = test.ci_job_url[/\d+\z/]
216
-
217
- "[#{ci_job_id}](#{test.ci_job_url})#{' ~"quarantine"' if test.quarantine?}"
218
- end.uniq.join(', ')
219
- end
220
-
221
- def generate_test_status(tests_with_same_testcase)
222
- tests_with_same_testcase.map(&:status).uniq.map do |status|
223
- %(~"#{status}")
224
- end.join(', ')
225
- end
226
-
227
- def generate_test_actions(tests_with_same_testcase)
228
- # All failed tests would be grouped together, meaning that
229
- # if one failed, all the tests here would be failed too.
230
- # So this check is safe. Same applies to 'passed'.
231
- # But all other status might be mixing together,
232
- # we cannot assume other statuses.
233
- if tests_with_same_testcase.first.status == 'failed'
234
- tests_having_failure_issue =
235
- tests_with_same_testcase.select(&:failure_issue)
236
-
237
- if tests_having_failure_issue.any?
238
- items = tests_having_failure_issue.uniq(&:failure_issue).map do |test|
239
- "<li>[ ] [failure issue](#{test.failure_issue})</li>"
240
- end.join(' ')
241
-
242
- "<ul>#{items}</ul>"
243
- else
244
- '<ul><li>[ ] failure issue exists or was created</li></ul>'
245
- end
246
- else
247
- '-'
248
- end
249
- end
250
-
251
- def generate_qa_issue_relation
252
- return unless Runtime::Env.qa_issue_url
253
-
254
- <<~MARKDOWN.chomp
255
- ## Release QA issue
256
-
257
- * #{Runtime::Env.qa_issue_url}
258
-
259
- /relate #{Runtime::Env.qa_issue_url}
260
- MARKDOWN
261
- end
262
-
263
- def generate_link_to_dashboard
264
- return unless Runtime::Env.qa_run_type
265
-
266
- <<~MARKDOWN.chomp
267
- ## Link to Grafana dashboard for run-type of #{Runtime::Env.qa_run_type}
268
-
269
- * https://dashboards.quality.gitlab.net/d/kuNYMgDnz/test-run-metrics?orgId=1&refresh=1m&var-run_type=#{Runtime::Env.qa_run_type}
270
- MARKDOWN
271
- end
272
- end
273
- end
274
- end
275
- end
@@ -1,190 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'gitlab'
4
-
5
- module Gitlab
6
- # Monkey patch the Gitlab client to use the correct API path and add required methods
7
- class Client
8
- def team_member(project, id)
9
- get("/projects/#{url_encode(project)}/members/all/#{id}")
10
- end
11
-
12
- def issue_discussions(project, issue_id, options = {})
13
- get("/projects/#{url_encode(project)}/issues/#{issue_id}/discussions", query: options)
14
- end
15
-
16
- def add_note_to_issue_discussion_as_thread(project, issue_id, discussion_id, options = {})
17
- post("/projects/#{url_encode(project)}/issues/#{issue_id}/discussions/#{discussion_id}/notes", query: options)
18
- end
19
- end
20
-
21
- module QA
22
- module Report
23
- # The GitLab client is used for API access: https://github.com/NARKOZ/gitlab
24
- class GitlabIssueClient
25
- MAINTAINER_ACCESS_LEVEL = 40
26
- RETRY_BACK_OFF_DELAY = 60
27
- MAX_RETRY_ATTEMPTS = 3
28
-
29
- def initialize(token:, project:)
30
- @token = token
31
- @project = project
32
- @retry_backoff = 0
33
-
34
- configure_gitlab_client
35
- end
36
-
37
- def assert_user_permission!
38
- handle_gitlab_client_exceptions do
39
- user = Gitlab.user
40
- member = Gitlab.team_member(project, user.id)
41
-
42
- abort_not_permitted if member.access_level < MAINTAINER_ACCESS_LEVEL
43
- end
44
- rescue Gitlab::Error::NotFound
45
- abort_not_permitted
46
- end
47
-
48
- def find_issues(iid: nil, options: {}, &select)
49
- select ||= :itself
50
-
51
- handle_gitlab_client_exceptions do
52
- return [Gitlab.issue(project, iid)].select(&select) if iid
53
-
54
- Gitlab.issues(project, options)
55
- .auto_paginate
56
- .select(&select)
57
- end
58
- end
59
-
60
- def find_issue_discussions(iid:)
61
- handle_gitlab_client_exceptions do
62
- Gitlab.issue_discussions(project, iid, order_by: 'created_at', sort: 'asc').auto_paginate
63
- end
64
- end
65
-
66
- def create_issue(title:, description:, labels:, issue_type: 'issue')
67
- attrs = { issue_type: issue_type, description: description, labels: labels }
68
-
69
- handle_gitlab_client_exceptions do
70
- Gitlab.create_issue(project, title, attrs)
71
- end
72
- end
73
-
74
- def edit_issue(iid:, options: {})
75
- handle_gitlab_client_exceptions do
76
- Gitlab.edit_issue(project, iid, options)
77
- end
78
- end
79
-
80
- def find_issue_notes(iid:)
81
- handle_gitlab_client_exceptions do
82
- Gitlab.issue_notes(project, iid, order_by: 'created_at', sort: 'asc')&.auto_paginate
83
- end
84
- end
85
-
86
- def create_issue_note(iid:, note:)
87
- handle_gitlab_client_exceptions do
88
- Gitlab.create_issue_note(project, iid, note)
89
- end
90
- end
91
-
92
- def edit_issue_note(issue_iid:, note_id:, note:)
93
- handle_gitlab_client_exceptions do
94
- Gitlab.edit_issue_note(project, issue_iid, note_id, note)
95
- end
96
- end
97
-
98
- def add_note_to_issue_discussion_as_thread(iid:, discussion_id:, body:)
99
- handle_gitlab_client_exceptions do
100
- Gitlab.add_note_to_issue_discussion_as_thread(project, iid, discussion_id, body: body)
101
- end
102
- end
103
-
104
- def find_user_id(username:)
105
- handle_gitlab_client_exceptions do
106
- user = Gitlab.users(username: username)&.first
107
- user['id'] unless user.nil?
108
- end
109
- end
110
-
111
- def upload_file(file_fullpath:)
112
- ignore_gitlab_client_exceptions do
113
- Gitlab.upload_file(project, file_fullpath)
114
- end
115
- end
116
-
117
- def ignore_gitlab_client_exceptions
118
- yield
119
- rescue StandardError, SystemCallError, OpenSSL::SSL::SSLError, Net::OpenTimeout, Net::ReadTimeout, Gitlab::Error::Error => e
120
- puts "Ignoring the following error: #{e}"
121
- end
122
-
123
- def handle_gitlab_client_exceptions
124
- yield
125
- rescue Gitlab::Error::NotFound
126
- # This error could be raised in assert_user_permission!
127
- # If so, we want it to terminate at that point
128
- raise
129
- rescue SystemCallError, OpenSSL::SSL::SSLError, Net::OpenTimeout, Net::ReadTimeout, Gitlab::Error::InternalServerError, Gitlab::Error::Parsing => e
130
- @retry_backoff += RETRY_BACK_OFF_DELAY
131
-
132
- raise if @retry_backoff > RETRY_BACK_OFF_DELAY * MAX_RETRY_ATTEMPTS
133
-
134
- warn_exception(e)
135
- warn("Sleeping for #{@retry_backoff} seconds before retrying...")
136
- sleep @retry_backoff
137
-
138
- retry
139
- rescue StandardError => e
140
- pipeline = QA::Runtime::Env.pipeline_from_project_name
141
- channel = case pipeline
142
- when "canary"
143
- "qa-production"
144
- when "staging-canary"
145
- "qa-staging"
146
- else
147
- "qa-#{pipeline}"
148
- end
149
- error_msg = warn_exception(e)
150
-
151
- return unless QA::Runtime::Env.ci_commit_ref_name == QA::Runtime::Env.default_branch
152
-
153
- slack_options = {
154
- channel: channel,
155
- icon_emoji: ':ci_failing:',
156
- message: <<~MSG
157
- An unexpected error occurred while reporting test results in issues.
158
- The error occurred in job: #{QA::Runtime::Env.ci_job_url}
159
- `#{error_msg}`
160
- MSG
161
- }
162
- puts "Posting Slack message to channel: #{channel}"
163
-
164
- Gitlab::QA::Slack::PostToSlack.new(**slack_options).invoke!
165
- end
166
-
167
- private
168
-
169
- attr_reader :token, :project
170
-
171
- def configure_gitlab_client
172
- Gitlab.configure do |config|
173
- config.endpoint = Runtime::Env.gitlab_api_base
174
- config.private_token = token
175
- end
176
- end
177
-
178
- def abort_not_permitted
179
- abort "You must have at least Maintainer access to the project to use this feature."
180
- end
181
-
182
- def warn_exception(error)
183
- error_msg = "#{error.class.name} #{error.message}"
184
- warn(error_msg)
185
- error_msg
186
- end
187
- end
188
- end
189
- end
190
- end
@@ -1,28 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Gitlab
4
- module QA
5
- module Report
6
- class GitlabIssueDryClient < GitlabIssueClient
7
- def create_issue(title:, description:, labels:, issue_type: 'issue')
8
- attrs = { description: description, labels: labels }
9
-
10
- puts "The following #{issue_type} would have been created:"
11
- puts "project: #{project}, title: #{title}, attrs: #{attrs}"
12
- end
13
-
14
- def edit_issue(iid:, options: {})
15
- puts "The #{project}##{iid} issue would have been updated with: #{options}"
16
- end
17
-
18
- def create_issue_note(iid:, note:)
19
- puts "The following note would have been posted on #{project}##{iid} issue: #{note}"
20
- end
21
-
22
- def add_note_to_issue_discussion_as_thread(iid:, discussion_id:, body:)
23
- puts "The following discussion note would have been posted on #{project}##{iid} (discussion #{discussion_id}) issue: #{body}"
24
- end
25
- end
26
- end
27
- end
28
- end
@@ -1,27 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'nokogiri'
4
-
5
- module Gitlab
6
- module QA
7
- module Report
8
- class JUnitTestResults < BaseTestResults
9
- def write
10
- # Ignore it for now
11
- end
12
-
13
- private
14
-
15
- def parse
16
- Nokogiri::XML.parse(File.read(path))
17
- end
18
-
19
- def process
20
- results.xpath('//testcase').map do |test|
21
- TestResult.from_junit(test)
22
- end
23
- end
24
- end
25
- end
26
- end
27
- end
@@ -1,29 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'json'
4
-
5
- module Gitlab
6
- module QA
7
- module Report
8
- class JsonTestResults < BaseTestResults
9
- def write
10
- json = results.merge('examples' => testcases.map(&:report))
11
-
12
- File.write(path, JSON.pretty_generate(json))
13
- end
14
-
15
- private
16
-
17
- def parse
18
- JSON.parse(File.read(path))
19
- end
20
-
21
- def process
22
- results['examples'].map do |test|
23
- TestResult.from_json(test)
24
- end
25
- end
26
- end
27
- end
28
- end
29
- end
@@ -1,86 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'nokogiri'
4
-
5
- module Gitlab
6
- module QA
7
- module Report
8
- class PrepareStageReports
9
- def initialize(input_files:)
10
- @input_files = input_files
11
- end
12
-
13
- # Create a new JUnit report file for each Stage, containing tests from that Stage alone
14
- def invoke!
15
- collate_test_cases(@input_files).each do |stage, tests|
16
- filename = "#{stage}.xml"
17
-
18
- File.write(filename, new_junit_report(tests))
19
-
20
- puts "Saved #{filename}"
21
- end
22
- end
23
-
24
- private
25
-
26
- def collate_test_cases(input_files)
27
- # Collect the test cases from the original reports and group them by Stage
28
- testcases = {}
29
-
30
- Dir.glob(input_files).each do |rspec_report_file|
31
- report = Nokogiri::XML(File.open(rspec_report_file))
32
- report.xpath('//testcase').each do |test|
33
- # The test file paths could start with any of
34
- # /qa/specs/features/api/<stage>
35
- # /qa/specs/features/browser_ui/<stage>
36
- # /qa/specs/features/ee/api/<stage>
37
- # /qa/specs/features/ee/browser_ui/<stage>
38
- # For now we assume the Stage is whatever follows api/ or browser_ui/
39
- test_path_match = test['file'].match(%r{(api|browser_ui)/([a-z0-9_]+)}i)
40
- next unless test_path_match
41
-
42
- stage = strip_number_prefix(test_path_match[2])
43
- testcases[stage] = [] unless testcases.key?(stage)
44
- testcases[stage] << test
45
- end
46
- end
47
-
48
- testcases
49
- end
50
-
51
- def strip_number_prefix(stage)
52
- stage.sub(/^\d+_/, '')
53
- end
54
-
55
- def new_junit_report(testcases)
56
- report = Nokogiri::XML::Document.new
57
- testsuite_node = report.create_element('testsuite', name: 'rspec', **collect_stats(testcases))
58
- report.root = testsuite_node
59
-
60
- testcases.each do |test|
61
- testsuite_node.add_child(test)
62
- end
63
-
64
- report.to_s
65
- end
66
-
67
- def collect_stats(testcases)
68
- stats = {
69
- tests: testcases.size,
70
- failures: 0,
71
- errors: 0,
72
- skipped: 0
73
- }
74
-
75
- testcases.each do |test|
76
- stats[:failures] += 1 unless test.search('failure').empty?
77
- stats[:errors] += 1 unless test.search('error').empty?
78
- stats[:skipped] += 1 unless test.search('skipped').empty?
79
- end
80
-
81
- stats
82
- end
83
- end
84
- end
85
- end
86
- end