gitlab_quality-test_tooling 2.5.0 → 2.7.0
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 +1 -1
- data/lib/gitlab_quality/test_tooling/gitlab_client/gitlab_client.rb +4 -3
- data/lib/gitlab_quality/test_tooling/gitlab_client/issues_client.rb +39 -0
- data/lib/gitlab_quality/test_tooling/report/concerns/issue_reports.rb +2 -2
- data/lib/gitlab_quality/test_tooling/report/relate_failure_issue.rb +161 -4
- data/lib/gitlab_quality/test_tooling/runtime/env.rb +3 -1
- data/lib/gitlab_quality/test_tooling/test_result/base_test_result.rb +3 -1
- 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: bc2b98142fc0891fe937da031f64a665ef016c153263b01b7c9820b284967800
|
|
4
|
+
data.tar.gz: 588d0cec55fb89405d61d6e41528343929d948fe6fb03717edf2e895fe727e5c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2402289c4b5282ad62289b7560b0faadc8cabc675d7e0262b38e6582cee5e2e6a2711ab053dc677d2e32d917c8a29608e8b90b485d977d4fe244c6a7e4c47505
|
|
7
|
+
data.tar.gz: 4bebf905075eb9b8ca2fb707c004d78746d8d8e518a6e10ee7d1085a2e3450b8a33d672a9ef382103deaf996591bd1f7c6b5d86f2249276a7a576a61074acbd7
|
data/Gemfile.lock
CHANGED
|
@@ -9,10 +9,11 @@ module GitlabQuality
|
|
|
9
9
|
RETRY_BACK_OFF_DELAY = 60
|
|
10
10
|
MAX_RETRY_ATTEMPTS = 3
|
|
11
11
|
|
|
12
|
-
def initialize(token:, project:, **_kwargs)
|
|
12
|
+
def initialize(token:, project:, endpoint: nil, **_kwargs)
|
|
13
13
|
@token = token
|
|
14
14
|
@project = project
|
|
15
15
|
@retry_backoff = 0
|
|
16
|
+
@endpoint = endpoint
|
|
16
17
|
end
|
|
17
18
|
|
|
18
19
|
def handle_gitlab_client_exceptions
|
|
@@ -76,11 +77,11 @@ module GitlabQuality
|
|
|
76
77
|
|
|
77
78
|
private
|
|
78
79
|
|
|
79
|
-
attr_reader :project, :token
|
|
80
|
+
attr_reader :project, :token, :endpoint
|
|
80
81
|
|
|
81
82
|
def client
|
|
82
83
|
@client ||= Gitlab.client(
|
|
83
|
-
endpoint: Runtime::Env.gitlab_api_base,
|
|
84
|
+
endpoint: endpoint || Runtime::Env.gitlab_api_base,
|
|
84
85
|
private_token: token
|
|
85
86
|
)
|
|
86
87
|
end
|
|
@@ -122,6 +122,45 @@ module GitlabQuality
|
|
|
122
122
|
end
|
|
123
123
|
end
|
|
124
124
|
|
|
125
|
+
def find_pipeline(project, pipeline_id)
|
|
126
|
+
handle_gitlab_client_exceptions do
|
|
127
|
+
client.pipeline(project, pipeline_id)
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def find_commit(project, sha)
|
|
132
|
+
handle_gitlab_client_exceptions do
|
|
133
|
+
client.commit(project, sha)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def find_commit_parent(project, sha)
|
|
138
|
+
handle_gitlab_client_exceptions do
|
|
139
|
+
# In a merged results commit, the first parent is the one from
|
|
140
|
+
# the main branch, and the second parent is from the branch
|
|
141
|
+
# itself (more likely to have caused the issue)
|
|
142
|
+
client.commit(project, sha).parent_ids.last
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def find_commit_diff(project, sha)
|
|
147
|
+
handle_gitlab_client_exceptions do
|
|
148
|
+
client.commit_diff(project, sha)
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def find_deployments(project, environment:, status:, order_by: 'id', sort: 'desc')
|
|
153
|
+
handle_gitlab_client_exceptions do
|
|
154
|
+
client.deployments(
|
|
155
|
+
project,
|
|
156
|
+
environment: environment,
|
|
157
|
+
status: status,
|
|
158
|
+
order_by: order_by,
|
|
159
|
+
sort: sort
|
|
160
|
+
)
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
125
164
|
private
|
|
126
165
|
|
|
127
166
|
attr_reader :token, :project
|
|
@@ -113,11 +113,11 @@ module GitlabQuality
|
|
|
113
113
|
end
|
|
114
114
|
end
|
|
115
115
|
|
|
116
|
-
def initial_reports_section(test)
|
|
116
|
+
def initial_reports_section(test, item_extra_content: nil)
|
|
117
117
|
<<~REPORTS
|
|
118
118
|
### Reports (1)
|
|
119
119
|
|
|
120
|
-
#{ReportsList.report_list_item(test)}
|
|
120
|
+
#{ReportsList.report_list_item(test, item_extra_content: item_extra_content)}
|
|
121
121
|
REPORTS
|
|
122
122
|
end
|
|
123
123
|
|
|
@@ -26,6 +26,28 @@ module GitlabQuality
|
|
|
26
26
|
NEW_ISSUE_LABELS = Set.new(%w[test failure::new priority::2 automation:bot-authored]).freeze
|
|
27
27
|
SCREENSHOT_IGNORED_ERRORS = ['500 Internal Server Error', 'fabricate_via_api!', 'Error Code 500'].freeze
|
|
28
28
|
|
|
29
|
+
# Map commits to security fork (gitlab-org/security/gitlab) for gitlab-org/gitlab since security patches exist
|
|
30
|
+
# there before being released to the public repository
|
|
31
|
+
DIFF_PROJECT_MAPPINGS = {
|
|
32
|
+
'gitlab-org/gitlab' => 'gitlab-org/security/gitlab',
|
|
33
|
+
'gitlab-org/customers-gitlab-com' => 'gitlab-org/customers-gitlab-com'
|
|
34
|
+
}.freeze
|
|
35
|
+
|
|
36
|
+
# The project contains record of the deployments we use to determine the commit diff
|
|
37
|
+
OPS_RELEASES_METADATA_PROJECT = 'gitlab-org/release/metadata'
|
|
38
|
+
|
|
39
|
+
# Naming of the environments are different between `gitlab-org/release/metadata` and `gitlab-org/quality`
|
|
40
|
+
# This maps the gitlab-org/quality environment names to the equivalent in `gitlab-org/release/metadata`
|
|
41
|
+
ENVIRONMENT_MAPPING = {
|
|
42
|
+
'production' => 'gprd',
|
|
43
|
+
'canary' => 'gprd-cny',
|
|
44
|
+
'staging' => 'gstg',
|
|
45
|
+
'staging-canary' => 'gstg-cny',
|
|
46
|
+
'staging-ref' => 'gstg-ref',
|
|
47
|
+
'preprod' => 'pre',
|
|
48
|
+
'release' => 'release'
|
|
49
|
+
}.freeze
|
|
50
|
+
|
|
29
51
|
MultipleIssuesFound = Class.new(StandardError)
|
|
30
52
|
|
|
31
53
|
def initialize(
|
|
@@ -47,7 +69,7 @@ module GitlabQuality
|
|
|
47
69
|
|
|
48
70
|
private
|
|
49
71
|
|
|
50
|
-
attr_reader :max_diff_ratio, :system_logs, :base_issue_labels, :exclude_labels_for_search, :metrics_files
|
|
72
|
+
attr_reader :max_diff_ratio, :system_logs, :base_issue_labels, :exclude_labels_for_search, :metrics_files, :ops_gitlab_client
|
|
51
73
|
|
|
52
74
|
def run!
|
|
53
75
|
puts "Reporting test failures in `#{files.join(',')}` as issues in project `#{project}` via the API at `#{Runtime::Env.gitlab_api_base}`."
|
|
@@ -271,14 +293,142 @@ module GitlabQuality
|
|
|
271
293
|
|
|
272
294
|
def new_issue_description(test)
|
|
273
295
|
super + [
|
|
274
|
-
"\n
|
|
296
|
+
"\n#{commit_diff_section}",
|
|
297
|
+
"### Stack trace",
|
|
275
298
|
"```\n#{test.full_stacktrace}\n```",
|
|
276
299
|
screenshot_section(test),
|
|
277
300
|
system_log_errors_section(test),
|
|
278
|
-
initial_reports_section(test)
|
|
301
|
+
initial_reports_section(test, item_extra_content: screenshot_artifact_url(test))
|
|
279
302
|
].compact.join("\n\n")
|
|
280
303
|
end
|
|
281
304
|
|
|
305
|
+
def commit_diff_section
|
|
306
|
+
"### Commit diff\n#{generate_diff_link}"
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
def generate_diff_link
|
|
310
|
+
initialize_gitlab_ops_client
|
|
311
|
+
|
|
312
|
+
if Runtime::Env.ci_pipeline_url.include?('ops.gitlab.net')
|
|
313
|
+
pipeline = ops_gitlab_client.find_pipeline(project, Runtime::Env.ci_pipeline_id.to_i)
|
|
314
|
+
generate_ops_gitlab_diff(pipeline)
|
|
315
|
+
else
|
|
316
|
+
pipeline = gitlab.find_pipeline(project, Runtime::Env.ci_pipeline_id.to_i)
|
|
317
|
+
generate_gitlab_diff(pipeline)
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
def generate_ops_gitlab_diff(pipeline)
|
|
322
|
+
deployment_info = fetch_deployment_info(pipeline)
|
|
323
|
+
|
|
324
|
+
return deployment_info if deployment_info.is_a?(String)
|
|
325
|
+
|
|
326
|
+
source_gitlab_ee_sha = fetch_deployment_gitlab_ee_sha(ops_gitlab_client, deployment_info[:source].sha)
|
|
327
|
+
target_gitlab_ee_sha = fetch_deployment_gitlab_ee_sha(ops_gitlab_client, deployment_info[:target].sha)
|
|
328
|
+
|
|
329
|
+
if source_gitlab_ee_sha == target_gitlab_ee_sha
|
|
330
|
+
"No diff"
|
|
331
|
+
else
|
|
332
|
+
"https://gitlab.com/gitlab-org/security/gitlab/-/compare/#{target_gitlab_ee_sha}...#{source_gitlab_ee_sha}"
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
def fetch_deployment_info(pipeline)
|
|
337
|
+
pipeline_deploy_version = Runtime::Env.ci_pipeline_name.match(/(\d+\.\d+\.\d+)(?:-|$)/)&.captures&.first
|
|
338
|
+
deployments = fetch_deployments(ops_gitlab_client, pipeline)
|
|
339
|
+
found_deployment = find_matching_deployment(pipeline_deploy_version, deployments)
|
|
340
|
+
|
|
341
|
+
return 'No matching deployment found to generate a diff link.' unless found_deployment
|
|
342
|
+
|
|
343
|
+
{
|
|
344
|
+
source: found_deployment[:deployment],
|
|
345
|
+
target: deployments[found_deployment[:index] + 1]
|
|
346
|
+
}
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
def fetch_deployments(client, pipeline)
|
|
350
|
+
ops_environment = pipeline.web_url.match(%r{gitlab-org/quality/([^/-]+(?:-[^/-]+)*?)(?:/-)?/pipelines})[1]
|
|
351
|
+
|
|
352
|
+
raise "Environment '#{ops_environment}' is not supported" unless ENVIRONMENT_MAPPING.key?(ops_environment)
|
|
353
|
+
|
|
354
|
+
environment = ENVIRONMENT_MAPPING[ops_environment]
|
|
355
|
+
|
|
356
|
+
client.find_deployments(
|
|
357
|
+
OPS_RELEASES_METADATA_PROJECT,
|
|
358
|
+
environment: environment,
|
|
359
|
+
status: 'success'
|
|
360
|
+
)
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
def initialize_gitlab_ops_client
|
|
364
|
+
@ops_gitlab_client = GitlabClient::IssuesClient.new(
|
|
365
|
+
endpoint: Runtime::Env.ci_api_v4_url,
|
|
366
|
+
token: Runtime::Env.gitlab_ci_token,
|
|
367
|
+
project: OPS_RELEASES_METADATA_PROJECT
|
|
368
|
+
)
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
def find_matching_deployment(pipeline_deploy_version, deployments)
|
|
372
|
+
return nil unless pipeline_deploy_version
|
|
373
|
+
return nil unless deployments
|
|
374
|
+
|
|
375
|
+
target_timestamp = extract_timestamp(pipeline_deploy_version)
|
|
376
|
+
return nil unless target_timestamp
|
|
377
|
+
|
|
378
|
+
matching_deployment = nil
|
|
379
|
+
|
|
380
|
+
deployments.each_with_index do |deployment, index|
|
|
381
|
+
deployment_version = extract_deployment_version(ops_gitlab_client, deployment)
|
|
382
|
+
next unless deployment_version
|
|
383
|
+
|
|
384
|
+
deployment_timestamp = extract_timestamp(deployment_version)
|
|
385
|
+
next unless deployment_timestamp
|
|
386
|
+
|
|
387
|
+
# Stop searching if the deployment timestamp is older than the target timestamp
|
|
388
|
+
break if deployment_timestamp && deployment_timestamp < target_timestamp
|
|
389
|
+
|
|
390
|
+
if deployment_version == pipeline_deploy_version
|
|
391
|
+
matching_deployment = { deployment: deployment, index: index }
|
|
392
|
+
break
|
|
393
|
+
end
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
matching_deployment
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
def extract_timestamp(version)
|
|
400
|
+
version.match(/\d{12}$/)&.to_s
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
def extract_deployment_version(client, deployment)
|
|
404
|
+
commit = client.find_commit(OPS_RELEASES_METADATA_PROJECT, deployment.sha)
|
|
405
|
+
version_match = commit.message&.match(/Product-Version: ([\d.]+)/)
|
|
406
|
+
version_match&.[](1)
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
def fetch_deployment_gitlab_ee_sha(client, deployment_sha)
|
|
410
|
+
commit_diff = client.find_commit_diff(OPS_RELEASES_METADATA_PROJECT, deployment_sha).first
|
|
411
|
+
diffs_content = commit_diff.diff.lines.select { |line| line.start_with?('+') }.map { |line| line[1..] }.join
|
|
412
|
+
|
|
413
|
+
begin
|
|
414
|
+
JSON.parse(diffs_content).dig('releases', 'gitlab-ee', 'sha')
|
|
415
|
+
rescue JSON::ParserError
|
|
416
|
+
raise "Failed to parse the diffs content"
|
|
417
|
+
end
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
def generate_gitlab_diff(pipeline)
|
|
421
|
+
pipeline_sha = pipeline.sha
|
|
422
|
+
parent_sha = gitlab.find_commit_parent(project, pipeline_sha)
|
|
423
|
+
diff_project = if DIFF_PROJECT_MAPPINGS.key?(project)
|
|
424
|
+
DIFF_PROJECT_MAPPINGS[project]
|
|
425
|
+
else
|
|
426
|
+
raise "Project #{project} is not supported for commit diff links"
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
"https://gitlab.com/#{diff_project}/-/compare/#{parent_sha}...#{pipeline_sha}"
|
|
430
|
+
end
|
|
431
|
+
|
|
282
432
|
def system_log_errors_section(test)
|
|
283
433
|
correlation_id = test.failures.first['correlation_id']
|
|
284
434
|
section = ''
|
|
@@ -322,7 +472,7 @@ module GitlabQuality
|
|
|
322
472
|
state_event = issue.state == 'closed' ? 'reopen' : nil
|
|
323
473
|
|
|
324
474
|
issue_attrs = {
|
|
325
|
-
description: increment_reports(current_reports_content: issue.description, test: test).to_s,
|
|
475
|
+
description: increment_reports(current_reports_content: issue.description, test: test, item_extra_content: screenshot_artifact_url(test)).to_s,
|
|
326
476
|
labels: up_to_date_labels(test: test, issue: issue)
|
|
327
477
|
}
|
|
328
478
|
issue_attrs[:state_event] = state_event if state_event
|
|
@@ -386,6 +536,13 @@ module GitlabQuality
|
|
|
386
536
|
.filter_map { |e| failure_to_ignore.find { |m| e['message'].include?(m) } }
|
|
387
537
|
.compact
|
|
388
538
|
end
|
|
539
|
+
|
|
540
|
+
def screenshot_artifact_url(test)
|
|
541
|
+
ci_job_url = test.ci_job_url
|
|
542
|
+
screenshot_path = test.screenshot_image[%r{qa/.*$}]
|
|
543
|
+
|
|
544
|
+
"|| [Screenshot](#{ci_job_url}/artifacts/file/#{screenshot_path})"
|
|
545
|
+
end
|
|
389
546
|
end
|
|
390
547
|
end
|
|
391
548
|
end
|
|
@@ -19,9 +19,11 @@ module GitlabQuality
|
|
|
19
19
|
'CI_PROJECT_NAME' => :ci_project_name,
|
|
20
20
|
'CI_PROJECT_PATH' => :ci_project_path,
|
|
21
21
|
'CI_PIPELINE_ID' => :ci_pipeline_id,
|
|
22
|
+
'CI_PIPELINE_NAME' => :ci_pipeline_name,
|
|
22
23
|
'CI_PIPELINE_URL' => :ci_pipeline_url,
|
|
23
24
|
'SLACK_QA_CHANNEL' => :slack_qa_channel,
|
|
24
|
-
'DEPLOY_VERSION' => :deploy_version
|
|
25
|
+
'DEPLOY_VERSION' => :deploy_version,
|
|
26
|
+
'QA_GITLAB_CI_TOKEN' => :gitlab_ci_token
|
|
25
27
|
}.freeze
|
|
26
28
|
|
|
27
29
|
ENV_VARIABLES.each do |env_name, method_name|
|
|
@@ -11,7 +11,9 @@ module GitlabQuality
|
|
|
11
11
|
"unexpected token at 'GitLab is not responding'",
|
|
12
12
|
"GitLab: Internal API error (502).",
|
|
13
13
|
"could not be found (502)",
|
|
14
|
-
"Error reference number: 502"
|
|
14
|
+
"Error reference number: 502",
|
|
15
|
+
"(502): `GitLab is not responding`",
|
|
16
|
+
"<head><title>502 Bad Gateway</title></head>"
|
|
15
17
|
].freeze
|
|
16
18
|
|
|
17
19
|
SHARED_EXAMPLES_CALLERS = %w[include_examples it_behaves_like].freeze
|
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: 2.
|
|
4
|
+
version: 2.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- GitLab Quality
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-
|
|
11
|
+
date: 2025-02-13 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: climate_control
|