gitlab_quality-test_tooling 0.8.3 → 0.9.1

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: 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