gitlab_quality-test_tooling 2.1.0 → 2.3.0

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: 69b4478a8c7af86d2dae63c79d0924037feb673486f1ae6f15d5617121a0b9f4
4
- data.tar.gz: 9a7f797d54687a515d834cfe0585bca7b87f5a28cd33000d5c06ebea292de56e
3
+ metadata.gz: 349ae941a991eb8ad7c3c863cc0b840c84541d32ff742948bf06796be3b5263a
4
+ data.tar.gz: f23b333320ea5b242d7bda47f895fa6d56c8c63f2206657353749980a7470b6e
5
5
  SHA512:
6
- metadata.gz: 50206a9f212b2d78ab38b8433ffb5f22864e8a322b6065b6492a9023104e2caf0097042c8935c913f7d16de982fd3fb04a8438d294865f61caac84eee60d943e
7
- data.tar.gz: 1267891667d8bf81617843d65ea6cd9bee2aef24007a2dc53ba92d970529f771dcbf8b8209aba7d59531f40ffac2e2c549444441dcae123c8e70bac1c0d417e7
6
+ metadata.gz: 489db862072fc1f574e35de9f5eae7c8adc0909a5c1c76d3e7d30d9f0c8633b9af0e5c0b7e26cc80802bf2ca8afb599ed151133ce1fb13b2fbe719a01a9e9ede
7
+ data.tar.gz: 4cba020c783aabde06a85dc48bd68b95f97b5df9fbe0471ff85f7e1d7788912404a2e996a1a95b54e6d276fc0e1c48f7d9b6202fb6f3231f85e5a4bd0da85a29
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- gitlab_quality-test_tooling (2.1.0)
4
+ gitlab_quality-test_tooling (2.3.0)
5
5
  activesupport (>= 7.0, < 7.2)
6
6
  amatch (~> 0.4.1)
7
7
  fog-google (~> 1.24, >= 1.24.1)
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
- reports.size
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
- sorted_reports[...LATEST_REPORTS_TO_SHOW].join("\n"),
63
- "<details><summary>See #{sorted_reports.size - LATEST_REPORTS_TO_SHOW} more reports</summary>",
64
- sorted_reports[LATEST_REPORTS_TO_SHOW..].join("\n"),
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 ||= reports.sort.reverse
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 <=>(other)
92
- to_s <=> other.to_s
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module GitlabQuality
4
4
  module TestTooling
5
- VERSION = "2.1.0"
5
+ VERSION = "2.3.0"
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: 2.1.0
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-10-22 00:00:00.000000000 Z
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