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 +4 -4
- data/Gemfile.lock +1 -1
- data/lib/gitlab_quality/test_tooling/report/relate_failure_issue.rb +40 -35
- data/lib/gitlab_quality/test_tooling/report/report_as_issue.rb +31 -1
- data/lib/gitlab_quality/test_tooling/report/report_results.rb +1 -1
- data/lib/gitlab_quality/test_tooling/report/results_in_issues.rb +1 -1
- data/lib/gitlab_quality/test_tooling/report/slow_test_issue.rb +11 -39
- data/lib/gitlab_quality/test_tooling/test_result/base_test_result.rb +35 -0
- data/lib/gitlab_quality/test_tooling/test_result/j_unit_test_result.rb +42 -0
- data/lib/gitlab_quality/test_tooling/test_result/json_test_result.rb +142 -0
- data/lib/gitlab_quality/test_tooling/test_results/j_unit_test_results.rb +1 -1
- data/lib/gitlab_quality/test_tooling/test_results/json_test_results.rb +1 -1
- data/lib/gitlab_quality/test_tooling/version.rb +1 -1
- metadata +5 -3
- data/lib/gitlab_quality/test_tooling/test_results/test_result.rb +0 -200
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 145ee9c8df7307958d5fa46edbff45de7ed0bb604dc213fb9ac5813cd1057327
|
4
|
+
data.tar.gz: 4308fca7357956cf7a236cd59337eb9677de2582f459c2a617da06ba157c60cb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d5a96691899147d6e61e6d98c24e63b8efe2e5d6cbf4870ea9c1095dfdc7d5091e9381def5a77a5b43dfa85e8c49b6b64bce34852f00b5a4dd61fbda6da70d10
|
7
|
+
data.tar.gz: daa62f692125939eb89b2ffe0664527bcc7078574327a874fe180889358d421686de8cbaeb22ddb83ef71247cea967f3b5d2dd3888415eb764569b2f701034e0
|
data/Gemfile.lock
CHANGED
@@ -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
|
-
|
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
|
-
|
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,
|
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
|
67
|
+
def systemic_failures_for_test_results(test_results)
|
68
68
|
test_results
|
69
|
-
.flat_map { |test| test.
|
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
|
-
|
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
|
-
|
184
|
-
|
179
|
+
first_failure = test.failures.first
|
180
|
+
|
181
|
+
if first_failure['message_lines'].empty?
|
182
|
+
first_failure['message']
|
185
183
|
else
|
186
|
-
|
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.
|
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,
|
385
|
-
return false
|
382
|
+
def should_report?(test, systemic_failure_messages)
|
383
|
+
return false unless test.failures?
|
386
384
|
|
387
|
-
puts " => Systemic
|
388
|
-
|
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
|
-
|
391
|
-
reason = ignore_failure_reason(test.report['exceptions'], exceptions_to_ignore)
|
388
|
+
reason = ignored_failure_reason(test.failures, failure_to_ignore)
|
392
389
|
|
393
|
-
|
394
|
-
|
390
|
+
if reason
|
391
|
+
puts " => Failure reporting skipped because #{reason}"
|
395
392
|
|
396
|
-
|
397
|
-
|
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>]
|
406
|
-
# @
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
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
|
-
|
414
|
-
|
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}
|
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
|
|
@@ -13,21 +13,11 @@ module GitlabQuality
|
|
13
13
|
include Concerns::FindSetDri
|
14
14
|
include Concerns::GroupAndCategoryLabels
|
15
15
|
|
16
|
-
NEW_ISSUE_LABELS = Set.new(
|
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
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
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
|
-
|
50
|
+
issues = find_issues(test, SEARCH_LABELS)
|
63
51
|
|
64
|
-
|
52
|
+
issues.each do |issue|
|
53
|
+
puts " => Existing issue link #{issue['web_url']}"
|
54
|
+
end
|
65
55
|
|
66
|
-
create_issue(test) unless
|
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
|
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.
|
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-
|
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
|