gitlab_quality-test_tooling 2.1.0 → 2.3.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/README.md +10 -0
- data/exe/detect-infrastructure-failures +31 -0
- data/lib/gitlab_quality/test_tooling/gitlab_client/job_client.rb +29 -0
- data/lib/gitlab_quality/test_tooling/job_trace_analyzer.rb +68 -0
- data/lib/gitlab_quality/test_tooling/report/concerns/issue_reports.rb +38 -11
- data/lib/gitlab_quality/test_tooling/test_results/json_test_results.rb +5 -0
- data/lib/gitlab_quality/test_tooling/version.rb +1 -1
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 349ae941a991eb8ad7c3c863cc0b840c84541d32ff742948bf06796be3b5263a
|
4
|
+
data.tar.gz: f23b333320ea5b242d7bda47f895fa6d56c8c63f2206657353749980a7470b6e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 489db862072fc1f574e35de9f5eae7c8adc0909a5c1c76d3e7d30d9f0c8633b9af0e5c0b7e26cc80802bf2ca8afb599ed151133ce1fb13b2fbe719a01a9e9ede
|
7
|
+
data.tar.gz: 4cba020c783aabde06a85dc48bd68b95f97b5df9fbe0471ff85f7e1d7788912404a2e996a1a95b54e6d276fc0e1c48f7d9b6202fb6f3231f85e5a4bd0da85a29
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -191,6 +191,16 @@ Usage: exe/existing-test-health-issue [options]
|
|
191
191
|
-h, --help Show the usage
|
192
192
|
```
|
193
193
|
|
194
|
+
### `exe/detect-infrastructure-failures`
|
195
|
+
|
196
|
+
```shell
|
197
|
+
Purpose: Checks wether a job failed on a known infrastructure error by parsing its trace.
|
198
|
+
Usage: exe/detect-infrastructure-failures [options]
|
199
|
+
-j, --job-id JOB_ID A valid Job ID
|
200
|
+
-p, --project PROJECT Can be an integer or a group/project string
|
201
|
+
-t, --token TOKEN A valid access token with `api` scope and Maintainer permission in PROJECT
|
202
|
+
```
|
203
|
+
|
194
204
|
### `exe/flaky-test-issues`
|
195
205
|
|
196
206
|
```shell
|
@@ -0,0 +1,31 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "bundler/setup"
|
5
|
+
require "optparse"
|
6
|
+
|
7
|
+
require_relative "../lib/gitlab_quality/test_tooling"
|
8
|
+
|
9
|
+
params = {}
|
10
|
+
|
11
|
+
OptionParser.new do |opts|
|
12
|
+
opts.banner = "Usage: #{$PROGRAM_NAME} [options]"
|
13
|
+
|
14
|
+
opts.on('-j', '--job-id JOB_ID', String, 'A valid job ID') do |job_id|
|
15
|
+
params[:job_id] = job_id
|
16
|
+
end
|
17
|
+
|
18
|
+
opts.on('-p', '--project PROJECT', String, 'Can be an integer or a group/project string') do |project|
|
19
|
+
params[:project] = project
|
20
|
+
end
|
21
|
+
|
22
|
+
opts.on('-t', '--token TOKEN', String, 'A valid access token with `api` scope and Maintainer permission in PROJECT') do |token|
|
23
|
+
params[:token] = token
|
24
|
+
end
|
25
|
+
|
26
|
+
opts.parse(ARGV)
|
27
|
+
end
|
28
|
+
|
29
|
+
raise ArgumentError, "Missing argument(s). Required arguments are: --job-id, --project, --token" if params.empty? || ([:job_id, :project, :token] - params.keys).any?
|
30
|
+
|
31
|
+
exit GitlabQuality::TestTooling::JobTraceAnalyzer.new(**params).found_infrastructure_error? ? 0 : 1
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'gitlab'
|
4
|
+
|
5
|
+
module GitlabQuality
|
6
|
+
module TestTooling
|
7
|
+
module GitlabClient
|
8
|
+
class JobClient < GitlabClient
|
9
|
+
attr_reader :job_id
|
10
|
+
|
11
|
+
def initialize(token:, project:, job_id:)
|
12
|
+
super
|
13
|
+
|
14
|
+
@job_id = job_id
|
15
|
+
end
|
16
|
+
|
17
|
+
def job_trace
|
18
|
+
trace = ''
|
19
|
+
|
20
|
+
ignore_gitlab_client_exceptions do
|
21
|
+
trace = client.job_trace(project, job_id)
|
22
|
+
end
|
23
|
+
|
24
|
+
trace
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'gitlab_client/job_client'
|
4
|
+
|
5
|
+
module GitlabQuality
|
6
|
+
module TestTooling
|
7
|
+
class JobTraceAnalyzer
|
8
|
+
attr_reader :project, :token, :job_id
|
9
|
+
|
10
|
+
TRANSIENT_ROOT_CAUSE_TO_TRACE_MAP =
|
11
|
+
{
|
12
|
+
failed_to_pull_image: ['job failed: failed to pull image'],
|
13
|
+
gitlab_com_overloaded: ['gitlab is currently unable to handle this request due to load'],
|
14
|
+
runner_disk_full: [
|
15
|
+
'no space left on device',
|
16
|
+
'Check free disk space'
|
17
|
+
],
|
18
|
+
job_timeout: [
|
19
|
+
'ERROR: Job failed: execution took longer than',
|
20
|
+
'Rspec suite is exceeding the 80 minute limit and is forced to exit with error'
|
21
|
+
],
|
22
|
+
gitaly: ['gitaly spawn failed'],
|
23
|
+
infrastructure: [
|
24
|
+
'the requested url returned error: 5', # any 5XX error code should be transient
|
25
|
+
'error: downloading artifacts from coordinator',
|
26
|
+
'error: uploading artifacts as "archive" to coordinator',
|
27
|
+
'500 Internal Server Error',
|
28
|
+
"Internal Server Error 500",
|
29
|
+
'502 Bad Gateway',
|
30
|
+
'503 Service Unavailable',
|
31
|
+
'Error: EEXIST: file already exists',
|
32
|
+
'Failed to connect to 127.0.0.1',
|
33
|
+
"Failed to open TCP connection to",
|
34
|
+
'connection reset by peer',
|
35
|
+
'segmentation fault',
|
36
|
+
'no space left on device',
|
37
|
+
'Check free disk space',
|
38
|
+
'CLUSTERDOWN'
|
39
|
+
],
|
40
|
+
flaky_test: [
|
41
|
+
"We have detected a PG::QueryCanceled error in the specs, so we're failing early"
|
42
|
+
]
|
43
|
+
}.freeze
|
44
|
+
|
45
|
+
def initialize(project:, token:, job_id:)
|
46
|
+
@project = project
|
47
|
+
@token = token
|
48
|
+
@job_id = job_id
|
49
|
+
end
|
50
|
+
|
51
|
+
def found_infrastructure_error?
|
52
|
+
TRANSIENT_ROOT_CAUSE_TO_TRACE_MAP[:infrastructure].any? do |search_string|
|
53
|
+
found = job_trace.downcase.include?(search_string.downcase)
|
54
|
+
|
55
|
+
puts "Found infrastructure error stacktrace: #{search_string}" if found
|
56
|
+
|
57
|
+
found
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def job_trace
|
64
|
+
@job_trace ||= GitlabClient::JobClient.new(project: project, token: token, job_id: job_id).job_trace
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -11,13 +11,15 @@ module GitlabQuality
|
|
11
11
|
FAILED_JOB_DESCRIPTION_REGEX = /First happened in #{JOB_URL_REGEX}\./m
|
12
12
|
REPORT_ITEM_REGEX = /^1\. (?<report_date>\d{4}-\d{2}-\d{2}): #{JOB_URL_REGEX} \((?<pipeline_url>\S+)\) ?(?<extra_content>.*)$/
|
13
13
|
LATEST_REPORTS_TO_SHOW = 10
|
14
|
+
DISPLAYED_HISTORY_REPORTS_THRESHOLD = 510 # https://gitlab.com/gitlab-org/quality/engineering-productivity/team/-/issues/587
|
14
15
|
DAILY_REPORTS_THRESHOLDS = 10
|
15
16
|
|
16
17
|
class ReportsList
|
17
|
-
def initialize(preserved_content:, section_header:, reports:, extra_content:)
|
18
|
+
def initialize(preserved_content:, section_header:, reports:, total_reports_count:, extra_content:)
|
18
19
|
@preserved_content = preserved_content
|
19
20
|
@section_header = section_header
|
20
21
|
@reports = reports
|
22
|
+
@total_reports_count = total_reports_count
|
21
23
|
@extra_content = extra_content
|
22
24
|
end
|
23
25
|
|
@@ -31,14 +33,14 @@ module GitlabQuality
|
|
31
33
|
end
|
32
34
|
|
33
35
|
def reports_count
|
34
|
-
|
36
|
+
total_reports_count
|
35
37
|
end
|
36
38
|
|
37
39
|
def to_s
|
38
40
|
[
|
39
41
|
preserved_content,
|
40
42
|
"#{section_header} (#{reports_count})",
|
41
|
-
reports_list,
|
43
|
+
reports_list(total_reports_count),
|
42
44
|
extra_content
|
43
45
|
].reject(&:blank?).compact.join("\n\n")
|
44
46
|
end
|
@@ -53,15 +55,16 @@ module GitlabQuality
|
|
53
55
|
|
54
56
|
private
|
55
57
|
|
56
|
-
attr_reader :preserved_content, :section_header, :reports, :extra_content
|
58
|
+
attr_reader :preserved_content, :section_header, :reports, :total_reports_count, :extra_content
|
57
59
|
|
58
|
-
def reports_list
|
60
|
+
def reports_list(total_reports_count)
|
59
61
|
if sorted_reports.size > LATEST_REPORTS_TO_SHOW
|
62
|
+
max_displayed_reports = DISPLAYED_HISTORY_REPORTS_THRESHOLD - LATEST_REPORTS_TO_SHOW
|
60
63
|
[
|
61
64
|
"Last #{LATEST_REPORTS_TO_SHOW} reports:",
|
62
|
-
|
63
|
-
"<details><summary>
|
64
|
-
|
65
|
+
displayed_reports[...LATEST_REPORTS_TO_SHOW].join("\n"),
|
66
|
+
"<details><summary>With #{total_reports_count - LATEST_REPORTS_TO_SHOW} more reports (displaying up to #{max_displayed_reports} reports) </summary>",
|
67
|
+
displayed_reports[LATEST_REPORTS_TO_SHOW..].join("\n"),
|
65
68
|
"</details>"
|
66
69
|
].join("\n\n")
|
67
70
|
else
|
@@ -70,7 +73,12 @@ module GitlabQuality
|
|
70
73
|
end
|
71
74
|
|
72
75
|
def sorted_reports
|
73
|
-
@sorted_reports ||=
|
76
|
+
@sorted_reports ||=
|
77
|
+
reports.sort_by { |report| [report.report_date, report.job_id, report.to_s] }.reverse
|
78
|
+
end
|
79
|
+
|
80
|
+
def displayed_reports
|
81
|
+
sorted_reports[0...DISPLAYED_HISTORY_REPORTS_THRESHOLD]
|
74
82
|
end
|
75
83
|
end
|
76
84
|
|
@@ -88,8 +96,8 @@ module GitlabQuality
|
|
88
96
|
"1. #{report_date}: #{job_url} (#{pipeline_url}) #{extra_content}".strip
|
89
97
|
end
|
90
98
|
|
91
|
-
def
|
92
|
-
|
99
|
+
def job_id
|
100
|
+
job_url.split('/').last
|
93
101
|
end
|
94
102
|
|
95
103
|
private
|
@@ -122,14 +130,33 @@ module GitlabQuality
|
|
122
130
|
preserved_content = current_reports_content.split(reports_section_header).first&.strip
|
123
131
|
reports = report_lines(current_reports_content) + [ReportsList.report_list_item(test, item_extra_content: item_extra_content)]
|
124
132
|
|
133
|
+
total_reports_count = increment_total_reports_count(
|
134
|
+
reports_section_header: reports_section_header,
|
135
|
+
content: current_reports_content,
|
136
|
+
reports: reports
|
137
|
+
)
|
138
|
+
|
125
139
|
ReportsList.new(
|
126
140
|
preserved_content: preserved_content,
|
127
141
|
section_header: reports_section_header,
|
128
142
|
reports: reports,
|
143
|
+
total_reports_count: total_reports_count,
|
129
144
|
extra_content: reports_extra_content
|
130
145
|
)
|
131
146
|
end
|
132
147
|
|
148
|
+
def increment_total_reports_count(reports_section_header:, content:, reports:)
|
149
|
+
reports_count = reports.count
|
150
|
+
|
151
|
+
return reports_count if reports_count < DISPLAYED_HISTORY_REPORTS_THRESHOLD
|
152
|
+
|
153
|
+
count_match = content.match(/#{Regexp.escape(reports_section_header)} \((\d+)\)/)
|
154
|
+
|
155
|
+
return reports_count unless count_match
|
156
|
+
|
157
|
+
count_match[1].to_i + 1
|
158
|
+
end
|
159
|
+
|
133
160
|
def failed_issue_job_url(issue)
|
134
161
|
job_urls_from_description(issue.description, REPORT_ITEM_REGEX).last ||
|
135
162
|
# Legacy format
|
@@ -16,9 +16,14 @@ module GitlabQuality
|
|
16
16
|
|
17
17
|
def parse
|
18
18
|
JSON.parse(File.read(path))
|
19
|
+
rescue JSON::ParserError
|
20
|
+
Runtime::Logger.debug("#{self.class.name}##{__method__} attempted to parse invalid JSON at path: #{path}")
|
21
|
+
{}
|
19
22
|
end
|
20
23
|
|
21
24
|
def process
|
25
|
+
return [] if results.empty?
|
26
|
+
|
22
27
|
results['examples'].map do |test|
|
23
28
|
GitlabQuality::TestTooling::TestResult::JsonTestResult.new(report: test, project: project, token: token)
|
24
29
|
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: 2.
|
4
|
+
version: 2.3.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: 2024-
|
11
|
+
date: 2024-11-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: climate_control
|
@@ -414,6 +414,7 @@ description: A collection of test-related tools.
|
|
414
414
|
email:
|
415
415
|
- quality@gitlab.com
|
416
416
|
executables:
|
417
|
+
- detect-infrastructure-failures
|
417
418
|
- existing-test-health-issue
|
418
419
|
- failed-test-issues
|
419
420
|
- flaky-test-issues
|
@@ -444,6 +445,7 @@ files:
|
|
444
445
|
- LICENSE.txt
|
445
446
|
- README.md
|
446
447
|
- Rakefile
|
448
|
+
- exe/detect-infrastructure-failures
|
447
449
|
- exe/existing-test-health-issue
|
448
450
|
- exe/failed-test-issues
|
449
451
|
- exe/flaky-test-issues
|
@@ -468,10 +470,12 @@ files:
|
|
468
470
|
- lib/gitlab_quality/test_tooling/gitlab_client/gitlab_client.rb
|
469
471
|
- lib/gitlab_quality/test_tooling/gitlab_client/issues_client.rb
|
470
472
|
- lib/gitlab_quality/test_tooling/gitlab_client/issues_dry_client.rb
|
473
|
+
- lib/gitlab_quality/test_tooling/gitlab_client/job_client.rb
|
471
474
|
- lib/gitlab_quality/test_tooling/gitlab_client/jobs_client.rb
|
472
475
|
- lib/gitlab_quality/test_tooling/gitlab_client/merge_requests_client.rb
|
473
476
|
- lib/gitlab_quality/test_tooling/gitlab_client/merge_requests_dry_client.rb
|
474
477
|
- lib/gitlab_quality/test_tooling/gitlab_client/repository_files_client.rb
|
478
|
+
- lib/gitlab_quality/test_tooling/job_trace_analyzer.rb
|
475
479
|
- lib/gitlab_quality/test_tooling/knapsack_reports/spec_run_time.rb
|
476
480
|
- lib/gitlab_quality/test_tooling/knapsack_reports/spec_run_time_report.rb
|
477
481
|
- lib/gitlab_quality/test_tooling/labels_inference.rb
|