gitlab_quality-test_tooling 0.8.3 → 0.9.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 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