gitlab_quality-test_tooling 0.8.3 → 0.9.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: a3472eb65666a6691797e9e0f0332a1773bb6babe898245a375bd9976523866e
4
- data.tar.gz: 6372e6df38afb56bbe300b39d25a65cb7cdd205765ded99cab3745f4fbe7d812
3
+ metadata.gz: e9bcb8c6202edb63313039592fdf1a6c953c0b43db4566d9e84105e31d2029d3
4
+ data.tar.gz: 27dcc6d668da50a3ec76bd13b8898d61b24bbe2835faeee6fc6dbce684945388
5
5
  SHA512:
6
- metadata.gz: 899483550e08f6f873d137d6a1bc90620ed58eff093827e76e5b4c92de8b7b1f95bba60181d2b726d79bf91b08b627cc93fb3817662dff73db8ba46e81557aad
7
- data.tar.gz: 6b21fae0eb76efa44dd868113d22d36c30a9f5d06b5bb1b625dd42129a463a24ae77abf4edf6df05d06800b29261116492cb39aa892490aaa22cfca9ce442aa4
6
+ metadata.gz: cd3003fced80af3cd8ebafa3495d799eb63756b659b4c6e002c27557ea84beb3ec4bba1bbb08784cf27fe74ab6f4d1c6eb2ed7888fdbf317209683475247310b
7
+ data.tar.gz: 579e87d66fa4107263220501f4959e3b08b0dbb887224ecb11917a69e5d6774e9cfdd2298d62a20992bc27b7b4be3f561e35574f599a4cbb165230331261e9c5
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- gitlab_quality-test_tooling (0.8.3)
4
+ gitlab_quality-test_tooling (0.9.0)
5
5
  activesupport (>= 6.1, < 7.1)
6
6
  gitlab (~> 4.19)
7
7
  http (~> 5.0)
@@ -27,7 +27,7 @@ module GitlabQuality
27
27
  FAILED_JOB_DESCRIPTION_REGEX = /First happened in #{JOB_URL_REGEX}\./m
28
28
  REPORT_ITEM_REGEX = /^1\. \d{4}-\d{2}-\d{2}: #{JOB_URL_REGEX} \((?<pipeline_url>.+)\)$/
29
29
  NEW_ISSUE_LABELS = Set.new(%w[test failure::new priority::2]).freeze
30
- IGNORE_EXCEPTIONS = [
30
+ IGNORED_FAILURES = [
31
31
  'Net::ReadTimeout',
32
32
  '403 Forbidden - Your account has been blocked'
33
33
  ].freeze
@@ -54,19 +54,19 @@ module GitlabQuality
54
54
  TestResults::Builder.new(files).test_results_per_file do |test_results|
55
55
  puts "=> Reporting #{test_results.count} tests in #{test_results.path}"
56
56
 
57
- systemic_exceptions = systemic_exceptions_for_test_results(test_results)
57
+ systemic_failures = systemic_failures_for_test_results(test_results)
58
58
 
59
59
  test_results.each do |test|
60
- relate_failure_to_issue(test) if should_report?(test, systemic_exceptions)
60
+ relate_failure_to_issue(test) if should_report?(test, systemic_failures)
61
61
  end
62
62
 
63
63
  test_results.write
64
64
  end
65
65
  end
66
66
 
67
- def systemic_exceptions_for_test_results(test_results)
67
+ def systemic_failures_for_test_results(test_results)
68
68
  test_results
69
- .flat_map { |test| test.report['exceptions']&.map { |exception| exception['message'] } }
69
+ .flat_map { |test| test.failures.map { |failure| failure['message'].lines.first.chomp } }
70
70
  .compact
71
71
  .tally
72
72
  .select { |_e, count| count >= SYSTEMIC_EXCEPTIONS_THRESHOLD }
@@ -172,18 +172,16 @@ module GitlabQuality
172
172
  end
173
173
 
174
174
  def failure_issues(test)
175
- search_labels = (base_issue_labels + Set.new(%w[test])).to_a
176
- gitlab.find_issues(options: { state: 'opened', labels: search_labels }).select do |issue|
177
- issue_title = issue.title.strip
178
- issue_title.include?(test.name) || issue_title.include?(partial_file_path(test.file))
179
- end
175
+ find_issues(test, (base_issue_labels + Set.new(%w[test])).to_a)
180
176
  end
181
177
 
182
178
  def full_stacktrace(test)
183
- if test.failures.first['message_lines'].empty? || test.failures.first['message_lines'].instance_of?(String)
184
- test.failures.first['message']
179
+ first_failure = test.failures.first
180
+
181
+ if first_failure['message_lines'].empty?
182
+ first_failure['message']
185
183
  else
186
- test.failures.first['message_lines'].join("\n")
184
+ first_failure['message_lines'].join("\n")
187
185
  end
188
186
  end
189
187
 
@@ -371,7 +369,7 @@ module GitlabQuality
371
369
  failure = full_stacktrace(test)
372
370
  return if SCREENSHOT_IGNORED_ERRORS.any? { |e| failure.include?(e) }
373
371
 
374
- relative_url = gitlab.upload_file(file_fullpath: test.failure_screenshot)
372
+ relative_url = gitlab.upload_file(file_fullpath: test.screenshot_image)
375
373
  return unless relative_url
376
374
 
377
375
  "### Screenshot\n\n#{relative_url.markdown}"
@@ -381,37 +379,44 @@ module GitlabQuality
381
379
  #
382
380
  # @return [TrueClass|FalseClass] false if the test was skipped or failed because of a transient error that can be ignored.
383
381
  # Otherwise returns true.
384
- def should_report?(test, systemic_exceptions)
385
- return false if test.failures.empty?
382
+ def should_report?(test, systemic_failure_messages)
383
+ return false unless test.failures?
386
384
 
387
- puts " => Systemic exceptions detected: #{systemic_exceptions}" if systemic_exceptions.any?
388
- exceptions_to_ignore = IGNORE_EXCEPTIONS + systemic_exceptions
385
+ puts " => Systemic failures detected: #{systemic_failure_messages}" if systemic_failure_messages.any?
386
+ failure_to_ignore = IGNORED_FAILURES + systemic_failure_messages
389
387
 
390
- if test.report.key?('exceptions')
391
- reason = ignore_failure_reason(test.report['exceptions'], exceptions_to_ignore)
388
+ reason = ignored_failure_reason(test.failures, failure_to_ignore)
392
389
 
393
- if reason
394
- puts " => Failure reporting skipped because #{reason}"
390
+ if reason
391
+ puts " => Failure reporting skipped because #{reason}"
395
392
 
396
- return false
397
- end
393
+ false
394
+ else
395
+ true
398
396
  end
399
-
400
- true
401
397
  end
402
398
 
403
399
  # Determine any reason to ignore a failure.
404
400
  #
405
- # @param [Array<Hash>] exceptions the exceptions associated with the failure.
406
- # @return [String] the reason to ignore the exceptions, or `nil` if any exceptions should not be ignored.
407
- def ignore_failure_reason(exceptions, ignored_exceptions)
408
- exception_messages = exceptions
409
- .filter_map { |exception| exception['message'] if ignored_exceptions.any? { |e| exception['message'].include?(e) } }
410
- .compact
411
- return if exception_messages.empty? || exception_messages.size < exceptions.size
401
+ # @param [Array<Hash>] failures the failures associated with the failure.
402
+ # @param [Array<String>] failure_to_ignore the failures messages that should be ignored.
403
+ # @return [String] the reason to ignore the failures, or `nil` if any failures should not be ignored.
404
+ def ignored_failure_reason(failures, failure_to_ignore)
405
+ failures_to_ignore = compute_ignored_failures(failures, failure_to_ignore)
406
+ return if failures_to_ignore.empty? || failures_to_ignore.size < failures.size
407
+
408
+ "the errors included: #{failures_to_ignore.map { |e| "`#{e}`" }.join(', ')}"
409
+ end
412
410
 
413
- msg = exception_messages.many? ? 'the errors were' : 'the error was'
414
- "#{msg} #{exception_messages.join(', ')}"
411
+ # Determine the failures that should be ignored based on a list of exception messages to ignore.
412
+ #
413
+ # @param [Array<Hash>] failures the failures associated with the failure.
414
+ # @param [Array<String>] failure_to_ignore the failures messages that should be ignored.
415
+ # @return [Array<String>] the exception messages to ignore, or `nil` if any failures should not be ignored.
416
+ def compute_ignored_failures(failures, failure_to_ignore)
417
+ failures
418
+ .filter_map { |e| failure_to_ignore.find { |m| e['message'].include?(m) } }
419
+ .compact
415
420
  end
416
421
  end
417
422
  end
@@ -50,7 +50,7 @@ module GitlabQuality
50
50
  def test_file_link(test)
51
51
  path_prefix = test.file.start_with?('qa/') ? 'qa/' : ''
52
52
 
53
- "[`#{path_prefix}#{test.file}`](#{FILE_BASE_URL}#{path_prefix}#{test.file}##{test.line_number})"
53
+ "[`#{path_prefix}#{test.file}`](#{FILE_BASE_URL}#{path_prefix}#{test.file}#L#{test.line_number})"
54
54
  end
55
55
 
56
56
  def new_issue_labels(_test)
@@ -128,6 +128,13 @@ module GitlabQuality
128
128
  labels
129
129
  end
130
130
 
131
+ def find_issues(test, labels)
132
+ gitlab.find_issues(options: { state: 'opened', labels: labels.to_a }).find_all do |issue|
133
+ issue_title = issue.title.strip
134
+ issue_title.include?(test.name) || issue_title.include?(partial_file_path(test.file))
135
+ end
136
+ end
137
+
131
138
  def pipeline_name_label
132
139
  case pipeline
133
140
  when 'production'
@@ -40,7 +40,7 @@ module GitlabQuality
40
40
  puts "Reporting tests in #{test_results.path}"
41
41
 
42
42
  test_results.each do |test|
43
- next if test.file.include?('/features/sanity/') || test.skipped
43
+ next if test.file.include?('/features/sanity/') || test.skipped?
44
44
 
45
45
  puts "Reporting test: #{test.file} | #{test.name}\n"
46
46
 
@@ -35,7 +35,7 @@ module GitlabQuality
35
35
  end
36
36
 
37
37
  def post_note(issue, test)
38
- return false if test.skipped
38
+ return false if test.skipped?
39
39
  return false if test.failures.empty?
40
40
 
41
41
  note = note_content(test)
@@ -59,24 +59,17 @@ module GitlabQuality
59
59
  def create_slow_issue(test)
60
60
  puts " => Finding existing issues for slow test '#{test.name}' (run time: #{test.run_time} seconds)..."
61
61
 
62
- issue = find_issue(test)
62
+ issues = find_issues(test, SEARCH_LABELS)
63
63
 
64
- puts " => Existing issue link #{issue['web_url']}" if issue.present?
64
+ issues.each do |issue|
65
+ puts " => Existing issue link #{issue['web_url']}"
66
+ end
65
67
 
66
- create_issue(test) unless issue.present?
68
+ create_issue(test) unless issues.any?
67
69
  rescue MultipleIssuesFound => e
68
70
  warn(e.message)
69
71
  end
70
72
 
71
- def find_issue(test)
72
- search_labels = SEARCH_LABELS
73
-
74
- gitlab.find_issues(options: { state: 'opened', labels: search_labels.to_a }).find do |issue|
75
- issue_title = issue.title.strip
76
- issue_title.include?(test.name) || issue_title.include?(partial_file_path(test.file))
77
- end
78
- end
79
-
80
73
  def should_create_slow_issue?(test)
81
74
  test.run_time > max_duration_for_test(test)
82
75
  end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GitlabQuality
4
+ module TestTooling
5
+ module TestResult
6
+ class BaseTestResult
7
+ attr_reader :report
8
+
9
+ def initialize(report)
10
+ @report = report
11
+ end
12
+
13
+ def stage
14
+ @stage ||= file[%r{(?:api|browser_ui)/(?:(?:\d+_)?(\w+))}, 1]
15
+ end
16
+
17
+ def name
18
+ raise NotImplementedError
19
+ end
20
+
21
+ def file
22
+ raise NotImplementedError
23
+ end
24
+
25
+ def skipped?
26
+ raise NotImplementedError
27
+ end
28
+
29
+ def failures
30
+ raise NotImplementedError
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GitlabQuality
4
+ module TestTooling
5
+ module TestResult
6
+ class JUnitTestResult < BaseTestResult
7
+ attr_accessor :testcase # Ignore it for now
8
+
9
+ def name
10
+ report['name']
11
+ end
12
+
13
+ def file
14
+ report['file'].delete_prefix('./')
15
+ end
16
+
17
+ def skipped?
18
+ report.search('skipped').any?
19
+ end
20
+
21
+ def failures # rubocop:disable Metrics/AbcSize
22
+ failures = report.search('failure')
23
+ return [] if failures.empty?
24
+
25
+ failures.map do |exception|
26
+ trace = exception.content.split("\n").map(&:strip)
27
+ spec_file_first_index = trace.rindex do |line|
28
+ line.include?(File.basename(report['file']))
29
+ end
30
+
31
+ exception['message'].gsub!(/(private_token=)[\w-]+/, '********')
32
+
33
+ {
34
+ 'message' => "#{exception['type']}: #{exception['message']}",
35
+ 'stacktrace' => trace.slice(0..spec_file_first_index).join("\n")
36
+ }
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GitlabQuality
4
+ module TestTooling
5
+ module TestResult
6
+ class JsonTestResult < BaseTestResult
7
+ PRIVATE_TOKEN_REGEX = /(private_token=)[\w-]+/
8
+
9
+ def name
10
+ report.fetch('full_description')
11
+ end
12
+
13
+ def file
14
+ report.fetch('file_path').delete_prefix('./')
15
+ end
16
+
17
+ def status
18
+ report.fetch('status')
19
+ end
20
+
21
+ def skipped?
22
+ status == 'pending'
23
+ end
24
+
25
+ def ci_job_url
26
+ report.fetch('ci_job_url', '')
27
+ end
28
+
29
+ def testcase
30
+ report.fetch('testcase', '')
31
+ end
32
+
33
+ def testcase=(new_testcase)
34
+ report['testcase'] = new_testcase
35
+ end
36
+
37
+ def failure_issue
38
+ report['failure_issue']
39
+ end
40
+
41
+ def failure_issue=(new_failure_issue)
42
+ report['failure_issue'] = new_failure_issue
43
+ end
44
+
45
+ def quarantine?
46
+ # The value for 'quarantine' could be nil, a hash, a string,
47
+ # or true (if the test just has the :quarantine tag)
48
+ # But any non-nil or false value should means the test is in quarantine
49
+ !!quarantine
50
+ end
51
+
52
+ def quarantine_type
53
+ quarantine['type'] if quarantine?
54
+ end
55
+
56
+ def quarantine_issue
57
+ quarantine['issue'] if quarantine?
58
+ end
59
+
60
+ def screenshot?
61
+ !!screenshot
62
+ end
63
+
64
+ def screenshot_image
65
+ screenshot['image'] if screenshot?
66
+ end
67
+
68
+ def product_group
69
+ report['product_group'].to_s
70
+ end
71
+
72
+ def product_group?
73
+ product_group != ''
74
+ end
75
+
76
+ def feature_category
77
+ report['feature_category']
78
+ end
79
+
80
+ def run_time
81
+ report['run_time'].to_f.round(2)
82
+ end
83
+
84
+ def example_id
85
+ report['id']
86
+ end
87
+
88
+ def line_number
89
+ report['line_number']
90
+ end
91
+
92
+ def failures # rubocop:disable Metrics/AbcSize
93
+ @failures ||=
94
+ report.fetch('exceptions', []).filter_map do |exception|
95
+ backtrace = exception['backtrace']
96
+ next unless backtrace.respond_to?(:rindex)
97
+
98
+ spec_file_first_index = backtrace.rindex do |line|
99
+ line.include?(File.basename(report['file_path']))
100
+ end
101
+
102
+ message = redact_private_token(exception['message'])
103
+ message_lines = Array(exception['message_lines']).map { |line| redact_private_token(line) }
104
+
105
+ {
106
+ 'message' => "#{exception['class']}: #{message}",
107
+ 'message_lines' => message_lines,
108
+ 'stacktrace' => "#{format_message_lines(message_lines)}\n#{backtrace.slice(0..spec_file_first_index).join("\n")}",
109
+ 'correlation_id' => exception['correlation_id']
110
+ }
111
+ end
112
+ end
113
+
114
+ def failures?
115
+ failures.any?
116
+ end
117
+
118
+ private
119
+
120
+ def quarantine
121
+ report.fetch('quarantine', nil)
122
+ end
123
+
124
+ def screenshot
125
+ report.fetch('screenshot', nil)
126
+ end
127
+
128
+ def format_message_lines(message_lines)
129
+ message_lines.is_a?(Array) ? message_lines.join("\n") : message_lines
130
+ end
131
+
132
+ def redact_private_token(text)
133
+ text.gsub(PRIVATE_TOKEN_REGEX, '********')
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
@@ -18,7 +18,7 @@ module GitlabQuality
18
18
 
19
19
  def process
20
20
  results.xpath('//testcase').map do |test|
21
- TestResult.from_junit(test)
21
+ GitlabQuality::TestTooling::TestResult::JUnitTestResult.new(test)
22
22
  end
23
23
  end
24
24
  end
@@ -20,7 +20,7 @@ module GitlabQuality
20
20
 
21
21
  def process
22
22
  results['examples'].map do |test|
23
- TestResult.from_json(test)
23
+ GitlabQuality::TestTooling::TestResult::JsonTestResult.new(test)
24
24
  end
25
25
  end
26
26
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module GitlabQuality
4
4
  module TestTooling
5
- VERSION = "0.8.3"
5
+ VERSION = "0.9.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: 0.8.3
4
+ version: 0.9.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: 2023-06-28 00:00:00.000000000 Z
11
+ date: 2023-07-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: climate_control
@@ -384,11 +384,13 @@ files:
384
384
  - lib/gitlab_quality/test_tooling/system_logs/log_types/rails/graphql_log.rb
385
385
  - lib/gitlab_quality/test_tooling/system_logs/shared_fields.rb
386
386
  - lib/gitlab_quality/test_tooling/system_logs/system_logs_formatter.rb
387
+ - lib/gitlab_quality/test_tooling/test_result/base_test_result.rb
388
+ - lib/gitlab_quality/test_tooling/test_result/j_unit_test_result.rb
389
+ - lib/gitlab_quality/test_tooling/test_result/json_test_result.rb
387
390
  - lib/gitlab_quality/test_tooling/test_results/base_test_results.rb
388
391
  - lib/gitlab_quality/test_tooling/test_results/builder.rb
389
392
  - lib/gitlab_quality/test_tooling/test_results/j_unit_test_results.rb
390
393
  - lib/gitlab_quality/test_tooling/test_results/json_test_results.rb
391
- - lib/gitlab_quality/test_tooling/test_results/test_result.rb
392
394
  - lib/gitlab_quality/test_tooling/version.rb
393
395
  - sig/gitlab_quality/test_tooling.rbs
394
396
  homepage: https://gitlab.com/gitlab-org/ruby/gems/gitlab_quality-test_tooling
@@ -1,200 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'active_support/core_ext/object/blank'
4
-
5
- module GitlabQuality
6
- module TestTooling
7
- module TestResults
8
- class TestResult
9
- def self.from_json(report)
10
- JsonTestResult.new(report)
11
- end
12
-
13
- def self.from_junit(report)
14
- JUnitTestResult.new(report)
15
- end
16
-
17
- attr_accessor :report, :failures
18
-
19
- def initialize(report)
20
- self.report = report
21
- self.failures = failures_from_exceptions
22
- end
23
-
24
- def stage
25
- @stage ||= file[%r{(?:api|browser_ui)/(?:(?:\d+_)?(\w+))}, 1]
26
- end
27
-
28
- def name
29
- raise NotImplementedError
30
- end
31
-
32
- def file
33
- raise NotImplementedError
34
- end
35
-
36
- def skipped
37
- raise NotImplementedError
38
- end
39
-
40
- private
41
-
42
- def failures_from_exceptions
43
- raise NotImplementedError
44
- end
45
-
46
- class JsonTestResult < TestResult
47
- def name
48
- report['full_description']
49
- end
50
-
51
- def file
52
- report['file_path'].delete_prefix('./')
53
- end
54
-
55
- def status
56
- report['status']
57
- end
58
-
59
- def ci_job_url
60
- report['ci_job_url']
61
- end
62
-
63
- def skipped
64
- status == 'pending'
65
- end
66
-
67
- def testcase
68
- report['testcase']
69
- end
70
-
71
- def testcase=(new_testcase)
72
- report['testcase'] = new_testcase
73
- end
74
-
75
- def failure_issue
76
- report['failure_issue']
77
- end
78
-
79
- def failure_issue=(new_failure_issue)
80
- report['failure_issue'] = new_failure_issue
81
- end
82
-
83
- def quarantine?
84
- # The value for 'quarantine' could be nil, a hash, a string,
85
- # or true (if the test just has the :quarantine tag)
86
- # But any non-nil or false value should means the test is in quarantine
87
- report['quarantine'].present?
88
- end
89
-
90
- def quarantine_type
91
- report['quarantine']['type'] if quarantine?
92
- end
93
-
94
- def quarantine_issue
95
- report['quarantine']['issue'] if quarantine?
96
- end
97
-
98
- def screenshot?
99
- report['screenshot'].present?
100
- end
101
-
102
- def failure_screenshot
103
- report['screenshot']['image'] if screenshot?
104
- end
105
-
106
- def product_group?
107
- report['product_group'].present?
108
- end
109
-
110
- def product_group
111
- report['product_group']
112
- end
113
-
114
- def feature_category
115
- report['feature_category']
116
- end
117
-
118
- def run_time
119
- report['run_time'].to_f.round(2)
120
- end
121
-
122
- def example_id
123
- report['id']
124
- end
125
-
126
- def line_number
127
- report['line_number']
128
- end
129
-
130
- private
131
-
132
- # rubocop:disable Metrics/AbcSize
133
- def failures_from_exceptions
134
- return [] unless report.key?('exceptions')
135
-
136
- report['exceptions'].map do |exception|
137
- spec_file_first_index = exception['backtrace'].rindex do |line|
138
- line.include?(File.basename(report['file_path']))
139
- end
140
-
141
- exception['message'].gsub!(/(private_token=)[\w-]+/, '********')
142
- Array(exception['message_lines']).each { |line| line.gsub!(/(private_token=)([\w-]+)/, '********') }
143
-
144
- {
145
- 'message' => "#{exception['class']}: #{exception['message']}",
146
- 'message_lines' => exception['message_lines'],
147
- 'stacktrace' => "#{format_message_lines(exception['message_lines'])}\n#{exception['backtrace'].slice(0..spec_file_first_index).join("\n")}",
148
- 'correlation_id' => exception['correlation_id']
149
- }
150
- end
151
- end
152
-
153
- def format_message_lines(message_lines)
154
- message_lines.is_a?(Array) ? message_lines.join("\n") : message_lines
155
- end
156
- # rubocop:enable Metrics/AbcSize
157
- end
158
-
159
- class JUnitTestResult < TestResult
160
- def name
161
- report['name']
162
- end
163
-
164
- def file
165
- report['file'].delete_prefix('./')
166
- end
167
-
168
- def skipped
169
- report.search('skipped').any?
170
- end
171
-
172
- attr_accessor :testcase # Ignore it for now
173
-
174
- private
175
-
176
- # rubocop:disable Metrics/AbcSize
177
- def failures_from_exceptions
178
- failures = report.search('failure')
179
- return [] if failures.empty?
180
-
181
- failures.map do |exception|
182
- trace = exception.content.split("\n").map(&:strip)
183
- spec_file_first_index = trace.rindex do |line|
184
- line.include?(File.basename(report['file']))
185
- end
186
-
187
- exception['message'].gsub!(/(private_token=)[\w-]+/, '********')
188
-
189
- {
190
- 'message' => "#{exception['type']}: #{exception['message']}",
191
- 'stacktrace' => trace.slice(0..spec_file_first_index).join("\n")
192
- }
193
- end
194
- end
195
- # rubocop:enable Metrics/AbcSize
196
- end
197
- end
198
- end
199
- end
200
- end