gitlab_quality-test_tooling 0.8.3 → 0.9.1

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: 145ee9c8df7307958d5fa46edbff45de7ed0bb604dc213fb9ac5813cd1057327
4
+ data.tar.gz: 4308fca7357956cf7a236cd59337eb9677de2582f459c2a617da06ba157c60cb
5
5
  SHA512:
6
- metadata.gz: 899483550e08f6f873d137d6a1bc90620ed58eff093827e76e5b4c92de8b7b1f95bba60181d2b726d79bf91b08b627cc93fb3817662dff73db8ba46e81557aad
7
- data.tar.gz: 6b21fae0eb76efa44dd868113d22d36c30a9f5d06b5bb1b625dd42129a463a24ae77abf4edf6df05d06800b29261116492cb39aa892490aaa22cfca9ce442aa4
6
+ metadata.gz: d5a96691899147d6e61e6d98c24e63b8efe2e5d6cbf4870ea9c1095dfdc7d5091e9381def5a77a5b43dfa85e8c49b6b64bce34852f00b5a4dd61fbda6da70d10
7
+ data.tar.gz: daa62f692125939eb89b2ffe0664527bcc7078574327a874fe180889358d421686de8cbaeb22ddb83ef71247cea967f3b5d2dd3888415eb764569b2f701034e0
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.1)
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
@@ -10,6 +10,17 @@ module GitlabQuality
10
10
 
11
11
  FILE_BASE_URL = "https://gitlab.com/gitlab-org/gitlab/-/blob/master/"
12
12
 
13
+ OTHER_TESTS_MAX_DURATION = 45.40 # seconds
14
+
15
+ TestLevelSpecification = Struct.new(:regex, :max_duration)
16
+
17
+ TEST_LEVEL_SPECIFICATIONS = [
18
+ TestLevelSpecification.new(%r{spec/features/}, 50.13),
19
+ TestLevelSpecification.new(%r{spec/(controllers|requests)/}, 19.20),
20
+ TestLevelSpecification.new(%r{spec/lib/}, 27.12),
21
+ TestLevelSpecification.new(%r{qa/specs/features/}, 240)
22
+ ].freeze
23
+
13
24
  def initialize(token:, input_files:, project: nil, dry_run: false, **_kwargs)
14
25
  @project = project
15
26
  @gitlab = (dry_run ? GitlabIssueDryClient : GitlabIssueClient).new(token: token, project: project)
@@ -42,7 +53,10 @@ module GitlabQuality
42
53
  | ------ | ------ |
43
54
  | File | #{test_file_link(test)} |
44
55
  | Description | `#{test.name}` |
56
+ | Test level | #{test.level} |
45
57
  | Hash | `#{test_hash(test)}` |
58
+ | Duration | #{test.run_time} seconds |
59
+ | Expected duration | < #{max_duration_for_test(test)} seconds |
46
60
  #{"| Test case | #{test.testcase} |" if test.testcase}
47
61
  DESCRIPTION
48
62
  end
@@ -50,7 +64,7 @@ module GitlabQuality
50
64
  def test_file_link(test)
51
65
  path_prefix = test.file.start_with?('qa/') ? 'qa/' : ''
52
66
 
53
- "[`#{path_prefix}#{test.file}`](#{FILE_BASE_URL}#{path_prefix}#{test.file}##{test.line_number})"
67
+ "[`#{path_prefix}#{test.file}#L#{test.line_number}`](#{FILE_BASE_URL}#{path_prefix}#{test.file}#L#{test.line_number})"
54
68
  end
55
69
 
56
70
  def new_issue_labels(_test)
@@ -128,6 +142,13 @@ module GitlabQuality
128
142
  labels
129
143
  end
130
144
 
145
+ def find_issues(test, labels)
146
+ gitlab.find_issues(options: { state: 'opened', labels: labels.to_a }).find_all do |issue|
147
+ issue_title = issue.title.strip
148
+ issue_title.include?(test.name) || issue_title.include?(partial_file_path(test.file))
149
+ end
150
+ end
151
+
131
152
  def pipeline_name_label
132
153
  case pipeline
133
154
  when 'production'
@@ -148,6 +169,15 @@ module GitlabQuality
148
169
  def ee_test?(test)
149
170
  test.file =~ %r{features/ee/(api|browser_ui)}
150
171
  end
172
+
173
+ def max_duration_for_test(test)
174
+ test_level_specification = TEST_LEVEL_SPECIFICATIONS.find do |test_level_specification|
175
+ test.example_id =~ test_level_specification.regex
176
+ end
177
+ return OTHER_TESTS_MAX_DURATION unless test_level_specification
178
+
179
+ test_level_specification.max_duration
180
+ end
151
181
  end
152
182
  end
153
183
  end
@@ -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)
@@ -13,21 +13,11 @@ module GitlabQuality
13
13
  include Concerns::FindSetDri
14
14
  include Concerns::GroupAndCategoryLabels
15
15
 
16
- NEW_ISSUE_LABELS = Set.new(%w[test type::maintenance maintenance::performance priority::3 severity::3]).freeze
16
+ NEW_ISSUE_LABELS = Set.new(['test', 'type::maintenance', 'maintenance::performance', 'priority::3', 'severity::3', 'rspec profiling', 'rspec:slow test']).freeze
17
17
  SEARCH_LABELS = %w[test maintenance::performance].freeze
18
18
 
19
19
  MultipleIssuesFound = Class.new(StandardError)
20
20
 
21
- TestLevelSpecification = Struct.new(:regex, :max_duration)
22
-
23
- OTHER_TESTS_MAX_DURATION = 45.40 # seconds
24
-
25
- TEST_LEVEL_SPECIFICATIONS = [
26
- TestLevelSpecification.new(%r{/features/}, 50.13),
27
- TestLevelSpecification.new(%r{/controllers|requests/}, 19.20),
28
- TestLevelSpecification.new(%r{/lib/}, 27.12)
29
- ].freeze
30
-
31
21
  private
32
22
 
33
23
  def run!
@@ -47,48 +37,30 @@ module GitlabQuality
47
37
  end
48
38
 
49
39
  def new_issue_description(test)
50
- super + [
51
- "\n### Slow test",
52
- "Slow tests detected, see guides for more details and how to improve them:",
53
- "- [Top slow tests](https://docs.gitlab.com/ee/development/testing_guide/best_practices.html#top-slow-tests)",
54
- "- [Test speed](https://docs.gitlab.com/ee/development/testing_guide/best_practices.html#test-speed)",
55
- "**Duration**: #{test.run_time} seconds"
56
- ].compact.join("\n\n")
40
+ super +
41
+ <<~DESCRIPTION
42
+ Slow tests were detected, please see the [test speed best practices guide](https://docs.gitlab.com/ee/development/testing_guide/best_practices.html#test-speed)
43
+ to improve them. More context available about this issue in the [top slow tests guide](https://docs.gitlab.com/ee/development/testing_guide/best_practices.html#top-slow-tests).
44
+ DESCRIPTION
57
45
  end
58
46
 
59
47
  def create_slow_issue(test)
60
48
  puts " => Finding existing issues for slow test '#{test.name}' (run time: #{test.run_time} seconds)..."
61
49
 
62
- issue = find_issue(test)
50
+ issues = find_issues(test, SEARCH_LABELS)
63
51
 
64
- puts " => Existing issue link #{issue['web_url']}" if issue.present?
52
+ issues.each do |issue|
53
+ puts " => Existing issue link #{issue['web_url']}"
54
+ end
65
55
 
66
- create_issue(test) unless issue.present?
56
+ create_issue(test) unless issues.any?
67
57
  rescue MultipleIssuesFound => e
68
58
  warn(e.message)
69
59
  end
70
60
 
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
61
  def should_create_slow_issue?(test)
81
62
  test.run_time > max_duration_for_test(test)
82
63
  end
83
-
84
- def max_duration_for_test(test)
85
- test_level_specification = TEST_LEVEL_SPECIFICATIONS.find do |test_level_specification|
86
- test.example_id =~ test_level_specification.regex
87
- end
88
- return OTHER_TESTS_MAX_DURATION unless test_level_specification
89
-
90
- test_level_specification.max_duration
91
- end
92
64
  end
93
65
  end
94
66
  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,142 @@
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 level
93
+ report['level']
94
+ end
95
+
96
+ def failures # rubocop:disable Metrics/AbcSize
97
+ @failures ||=
98
+ report.fetch('exceptions', []).filter_map do |exception|
99
+ backtrace = exception['backtrace']
100
+ next unless backtrace.respond_to?(:rindex)
101
+
102
+ spec_file_first_index = backtrace.rindex do |line|
103
+ line.include?(File.basename(report['file_path']))
104
+ end
105
+
106
+ message = redact_private_token(exception['message'])
107
+ message_lines = Array(exception['message_lines']).map { |line| redact_private_token(line) }
108
+
109
+ {
110
+ 'message' => "#{exception['class']}: #{message}",
111
+ 'message_lines' => message_lines,
112
+ 'stacktrace' => "#{format_message_lines(message_lines)}\n#{backtrace.slice(0..spec_file_first_index).join("\n")}",
113
+ 'correlation_id' => exception['correlation_id']
114
+ }
115
+ end
116
+ end
117
+
118
+ def failures?
119
+ failures.any?
120
+ end
121
+
122
+ private
123
+
124
+ def quarantine
125
+ report.fetch('quarantine', nil)
126
+ end
127
+
128
+ def screenshot
129
+ report.fetch('screenshot', nil)
130
+ end
131
+
132
+ def format_message_lines(message_lines)
133
+ message_lines.is_a?(Array) ? message_lines.join("\n") : message_lines
134
+ end
135
+
136
+ def redact_private_token(text)
137
+ text.gsub(PRIVATE_TOKEN_REGEX, '********')
138
+ end
139
+ end
140
+ end
141
+ end
142
+ 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.1"
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.1
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-13 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