gitlab_quality-test_tooling 1.23.0 → 1.24.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 +4 -4
- data/Gemfile.lock +5 -2
- data/lib/gitlab_quality/test_tooling/concerns/influxdb_tools.rb +29 -0
- data/lib/gitlab_quality/test_tooling/concerns/test_metrics.rb +145 -0
- data/lib/gitlab_quality/test_tooling/report/flaky_test_issue.rb +13 -4
- data/lib/gitlab_quality/test_tooling/report/slow_test_issue.rb +23 -0
- data/lib/gitlab_quality/test_tooling/test_metric/log_test_metrics.rb +76 -0
- data/lib/gitlab_quality/test_tooling/version.rb +1 -1
- metadata +40 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '009e41374e42e637a571410da7cd95aa23ab4d5a6f1599f080a0ccea8432b820'
|
4
|
+
data.tar.gz: b81d4043e16bc5da888a102d3765250691acb705bb3edac3762c6baad0fe490f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f1d34b6b4862000282c042e25b5f0cd02e23e72dee4307d0eb9d2309a0e5d352dfeeeadab2f5da7d01b91fe798a192bf8f2ed075608e9f73854842d107bef55a
|
7
|
+
data.tar.gz: eb3746a140cb622b9ede27514cb26ea7681b1bafffc5320dd9ce6dce83951db2e752f5458fa7af09906488fd6ef871c589c232794a4bf3d8a6962613cc35e484
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
gitlab_quality-test_tooling (1.
|
4
|
+
gitlab_quality-test_tooling (1.24.0)
|
5
5
|
activesupport (>= 6.1, < 7.2)
|
6
6
|
amatch (~> 0.4.1)
|
7
7
|
gitlab (~> 4.19)
|
@@ -130,6 +130,7 @@ GEM
|
|
130
130
|
multi_xml (>= 0.5.2)
|
131
131
|
i18n (1.14.4)
|
132
132
|
concurrent-ruby (~> 1.0)
|
133
|
+
influxdb-client (3.1.0)
|
133
134
|
jaro_winkler (1.5.6)
|
134
135
|
json (2.7.1)
|
135
136
|
kramdown (2.4.0)
|
@@ -312,11 +313,13 @@ PLATFORMS
|
|
312
313
|
ruby
|
313
314
|
|
314
315
|
DEPENDENCIES
|
316
|
+
activesupport (>= 6.1, < 7.2)
|
315
317
|
climate_control (~> 1.2)
|
316
318
|
gitlab-dangerfiles (~> 3.8)
|
317
319
|
gitlab-styles (~> 10.0)
|
318
320
|
gitlab_quality-test_tooling!
|
319
321
|
guard-rspec (~> 4.7)
|
322
|
+
influxdb-client (~> 3.1)
|
320
323
|
lefthook (~> 1.3)
|
321
324
|
pry-byebug (= 3.10.1)
|
322
325
|
rake (~> 13.0)
|
@@ -328,4 +331,4 @@ DEPENDENCIES
|
|
328
331
|
webmock (= 3.7.0)
|
329
332
|
|
330
333
|
BUNDLED WITH
|
331
|
-
2.5.
|
334
|
+
2.5.6
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GitlabQuality
|
4
|
+
module TestTooling
|
5
|
+
module Concerns
|
6
|
+
module InfluxdbTools
|
7
|
+
# InfluxDb client
|
8
|
+
#
|
9
|
+
# @return [InfluxDB2::Client]
|
10
|
+
def influx_client(url:, token:, bucket:)
|
11
|
+
@influx_client ||= InfluxDB2::Client.new(
|
12
|
+
url || raise('Missing influxdb_url'),
|
13
|
+
token || raise('Missing influxdb_token'),
|
14
|
+
bucket: bucket || raise('Missing influxdb_bucket'),
|
15
|
+
org: "gitlab-qa",
|
16
|
+
precision: InfluxDB2::WritePrecision::NANOSECOND
|
17
|
+
)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Write client
|
21
|
+
#
|
22
|
+
# @return [WriteApi]
|
23
|
+
def write_api(url:, token:, bucket:)
|
24
|
+
@write_api ||= influx_client(url: url, token: token, bucket: bucket).create_write_api
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GitlabQuality
|
4
|
+
module TestTooling
|
5
|
+
module Concerns
|
6
|
+
module TestMetrics
|
7
|
+
# Single common timestamp for all exported example metrics to keep data points consistently grouped
|
8
|
+
#
|
9
|
+
# @return [Time]
|
10
|
+
def time
|
11
|
+
return @time if defined?(@time)
|
12
|
+
|
13
|
+
created_at = Time.strptime(env('CI_PIPELINE_CREATED_AT'), '%Y-%m-%dT%H:%M:%S%z') if env('CI_PIPELINE_CREATED_AT')
|
14
|
+
@time = (created_at || Time.now).utc.strftime('%Y-%m-%dT%H:%M:%S%z')
|
15
|
+
end
|
16
|
+
|
17
|
+
# rubocop:disable Metrics/AbcSize
|
18
|
+
# Metrics tags
|
19
|
+
#
|
20
|
+
# @param [RSpec::Core::Example] example
|
21
|
+
# @param [Array<String>] custom_keys
|
22
|
+
# @param [String]
|
23
|
+
# @return [Hash]
|
24
|
+
def tags(example, custom_keys, run_type)
|
25
|
+
{
|
26
|
+
name: example.full_description,
|
27
|
+
file_path: example.metadata[:file_path].sub(/\A./, ''),
|
28
|
+
status: status(example),
|
29
|
+
quarantined: quarantined(example),
|
30
|
+
job_name: job_name,
|
31
|
+
merge_request: merge_request,
|
32
|
+
run_type: run_type,
|
33
|
+
stage: example.metadata[:product_stage] || example.metadata[:product_category],
|
34
|
+
product_group: example.metadata[:product_group],
|
35
|
+
exception_class: example.execution_result.exception&.class&.to_s,
|
36
|
+
**custom_metrics(example.metadata, custom_keys)
|
37
|
+
}.compact
|
38
|
+
end
|
39
|
+
# rubocop:enable Metrics/AbcSize
|
40
|
+
|
41
|
+
# Metrics fields
|
42
|
+
#
|
43
|
+
# @param [RSpec::Core::Example] example
|
44
|
+
# @param [Array<String>] custom_keys
|
45
|
+
# @return [Hash]
|
46
|
+
def fields(example, custom_keys)
|
47
|
+
{
|
48
|
+
id: example.id,
|
49
|
+
run_time: (example.execution_result.run_time * 1000).round,
|
50
|
+
job_url: Runtime::Env.ci_job_url,
|
51
|
+
pipeline_url: env('CI_PIPELINE_URL'),
|
52
|
+
pipeline_id: env('CI_PIPELINE_ID'),
|
53
|
+
job_id: env('CI_JOB_ID'),
|
54
|
+
merge_request_iid: merge_request_iid,
|
55
|
+
failure_exception: example.execution_result.exception.to_s.delete("\n"),
|
56
|
+
**custom_metrics(example.metadata, custom_keys)
|
57
|
+
}.compact
|
58
|
+
end
|
59
|
+
|
60
|
+
# Return a more detailed status
|
61
|
+
#
|
62
|
+
# - if test is failed or pending, return rspec status
|
63
|
+
# - if test passed but had more than 1 attempt, consider test flaky
|
64
|
+
#
|
65
|
+
# @param [RSpec::Core::Example] example
|
66
|
+
# @return [Symbol]
|
67
|
+
def status(example)
|
68
|
+
rspec_status = example.execution_result.status
|
69
|
+
return rspec_status if [:pending, :failed].include?(rspec_status)
|
70
|
+
|
71
|
+
retry_attempts(example.metadata).positive? ? :flaky : :passed
|
72
|
+
end
|
73
|
+
|
74
|
+
# Retry attempts
|
75
|
+
#
|
76
|
+
# @param [Hash] example
|
77
|
+
# @return [Integer]
|
78
|
+
def retry_attempts(metadata)
|
79
|
+
metadata[:retry_attempts] || 0
|
80
|
+
end
|
81
|
+
|
82
|
+
# Checks if spec is quarantined
|
83
|
+
#
|
84
|
+
# @param [RSpec::Core::Example] example
|
85
|
+
# @return [String]
|
86
|
+
def quarantined(example)
|
87
|
+
return "false" unless example.metadata.key?(:quarantine)
|
88
|
+
|
89
|
+
# if quarantine key is present and status is pending, consider it quarantined
|
90
|
+
(example.execution_result.status == :pending).to_s
|
91
|
+
end
|
92
|
+
|
93
|
+
# Base ci job name
|
94
|
+
#
|
95
|
+
# @return [String]
|
96
|
+
def job_name
|
97
|
+
@job_name ||= Runtime::Env.ci_job_name&.gsub(%r{ \d{1,2}/\d{1,2}}, '')
|
98
|
+
end
|
99
|
+
|
100
|
+
# Check if it is a merge request execution
|
101
|
+
#
|
102
|
+
# @return [String]
|
103
|
+
def merge_request
|
104
|
+
(!!merge_request_iid).to_s
|
105
|
+
end
|
106
|
+
|
107
|
+
# Merge request iid
|
108
|
+
#
|
109
|
+
# @return [String]
|
110
|
+
def merge_request_iid
|
111
|
+
env('CI_MERGE_REQUEST_IID') || env('TOP_UPSTREAM_MERGE_REQUEST_IID')
|
112
|
+
end
|
113
|
+
|
114
|
+
# Custom test metrics
|
115
|
+
#
|
116
|
+
# @param [Hash] metadata
|
117
|
+
# @param [Array] array of custom metrics keys
|
118
|
+
# @return [Hash]
|
119
|
+
def custom_metrics(metadata, custom_keys)
|
120
|
+
return {} if custom_keys.nil?
|
121
|
+
|
122
|
+
custom_metrics = {}
|
123
|
+
custom_keys.each do |k|
|
124
|
+
value = metadata[k.to_sym]
|
125
|
+
v = value.is_a?(Numeric) || value.nil? ? value : value.to_s
|
126
|
+
|
127
|
+
custom_metrics[k.to_sym] = v
|
128
|
+
end
|
129
|
+
|
130
|
+
custom_metrics
|
131
|
+
end
|
132
|
+
|
133
|
+
# Return non empty environment variable value
|
134
|
+
#
|
135
|
+
# @param [String] name
|
136
|
+
# @return [String, nil]
|
137
|
+
def env(name)
|
138
|
+
return unless ENV[name] && !ENV[name].empty?
|
139
|
+
|
140
|
+
ENV.fetch(name)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
@@ -22,7 +22,7 @@ module GitlabQuality
|
|
22
22
|
FOUND_IN_MASTER_LABEL = '~"found:master"'
|
23
23
|
REPORT_SECTION_HEADER = '### Flakiness reports'
|
24
24
|
REPORTS_DOCUMENTATION = <<~DOC
|
25
|
-
Flaky tests were detected
|
25
|
+
Flaky tests were detected. Please refer to the [Flaky tests reproducibility instructions](https://docs.gitlab.com/ee/development/testing_guide/flaky_tests.html#how-to-reproduce-a-flaky-test-locally)
|
26
26
|
to learn more about how to reproduce them.
|
27
27
|
DOC
|
28
28
|
|
@@ -128,13 +128,22 @@ module GitlabQuality
|
|
128
128
|
end
|
129
129
|
end
|
130
130
|
|
131
|
+
# The report count is based on the percentiles of flakiness issues.
|
132
|
+
#
|
133
|
+
# See https://gitlab.com/gitlab-org/quality/engineering-productivity/team/-/blob/main/scripts/unhealthy_test_issues_statistics.rb
|
134
|
+
# to gather these statistics.
|
135
|
+
#
|
136
|
+
# P75 => flakiness::4
|
137
|
+
# P90 => flakiness::3
|
138
|
+
# P95 => flakiness::2
|
139
|
+
# Above P95 => flakiness::1
|
131
140
|
def flakiness_status_labels_quick_action(reports_count)
|
132
141
|
case reports_count
|
133
|
-
when
|
142
|
+
when 42..Float::INFINITY
|
134
143
|
'/label ~"flakiness::1"'
|
135
|
-
when
|
144
|
+
when 22..41
|
136
145
|
'/label ~"flakiness::2"'
|
137
|
-
when
|
146
|
+
when 4..21
|
138
147
|
'/label ~"flakiness::3"'
|
139
148
|
else
|
140
149
|
'/label ~"flakiness::4"'
|
@@ -69,6 +69,7 @@ module GitlabQuality
|
|
69
69
|
|
70
70
|
note_body = [
|
71
71
|
new_reports_list.to_s,
|
72
|
+
slowness_status_labels_quick_action(new_reports_list.reports_count),
|
72
73
|
identity_labels_quick_action,
|
73
74
|
relate_issues_quick_actions(related_issues)
|
74
75
|
].join("\n")
|
@@ -108,6 +109,28 @@ module GitlabQuality
|
|
108
109
|
end
|
109
110
|
end
|
110
111
|
|
112
|
+
# The report count is based on the percentiles of slowness issues.
|
113
|
+
#
|
114
|
+
# See https://gitlab.com/gitlab-org/quality/engineering-productivity/team/-/blob/main/scripts/unhealthy_test_issues_statistics.rb
|
115
|
+
# to gather these statistics.
|
116
|
+
#
|
117
|
+
# P75 => slowness::4
|
118
|
+
# P90 => slowness::3
|
119
|
+
# P95 => slowness::2
|
120
|
+
# Above P95 => slowness::1
|
121
|
+
def slowness_status_labels_quick_action(reports_count)
|
122
|
+
case reports_count
|
123
|
+
when 40..Float::INFINITY
|
124
|
+
'/label ~"slowness::1"'
|
125
|
+
when 28..39
|
126
|
+
'/label ~"slowness::2"'
|
127
|
+
when 12..27
|
128
|
+
'/label ~"slowness::3"'
|
129
|
+
else
|
130
|
+
'/label ~"slowness::4"'
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
111
134
|
def identity_labels_quick_action
|
112
135
|
labels_list = IDENTITY_LABELS.map { |label| %(~"#{label}") }.join(' ')
|
113
136
|
%(/label #{labels_list})
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module GitlabQuality
|
6
|
+
module TestTooling
|
7
|
+
module TestMetric
|
8
|
+
class LogTestMetrics
|
9
|
+
include Concerns::TestMetrics
|
10
|
+
include Concerns::InfluxdbTools
|
11
|
+
|
12
|
+
CUSTOM_METRICS_KEY = :custom_test_metrics
|
13
|
+
|
14
|
+
def initialize(examples:, influxdb_url: nil, influxdb_token: nil, influxdb_bucket: nil, run_type: nil)
|
15
|
+
@examples = examples
|
16
|
+
@influxdb_url = influxdb_url
|
17
|
+
@influxdb_token = influxdb_token
|
18
|
+
@influxdb_bucket = influxdb_bucket
|
19
|
+
@run_type = run_type
|
20
|
+
end
|
21
|
+
|
22
|
+
# Push test execution metrics to influxdb
|
23
|
+
#
|
24
|
+
# @param [Array<String>] custom_keys_tags
|
25
|
+
# @param [Array<String>] custom_keys_fields
|
26
|
+
# @return [nil]
|
27
|
+
def push_test_metrics(custom_keys_tags: nil, custom_keys_fields: nil)
|
28
|
+
@test_metrics ||= examples.filter_map { |example| parse_test_results(example, custom_keys_tags, custom_keys_fields) }
|
29
|
+
|
30
|
+
write_api(url: influxdb_url, token: influxdb_token, bucket: influxdb_bucket).write(data: test_metrics)
|
31
|
+
Runtime::Logger.debug("Pushed #{test_metrics.length} test execution entries to influxdb")
|
32
|
+
rescue StandardError => e
|
33
|
+
Runtime::Logger.error("Failed to push test execution metrics to influxdb, error: #{e}")
|
34
|
+
end
|
35
|
+
|
36
|
+
# Save metrics in json file
|
37
|
+
#
|
38
|
+
# @param [String] file_name
|
39
|
+
# @param [Array<String>] custom_keys_tags
|
40
|
+
# @param [Array<String>] custom_keys_fields
|
41
|
+
# @return [nil]
|
42
|
+
def save_test_metrics(file_name, custom_keys_tags: nil, custom_keys_fields: nil)
|
43
|
+
@test_metrics ||= examples.filter_map { |example| parse_test_results(example, custom_keys_tags, custom_keys_fields) }
|
44
|
+
file = "tmp/#{file_name}"
|
45
|
+
|
46
|
+
File.write(file, test_metrics.to_json) && Runtime::Logger.debug("Saved test metrics to #{file}")
|
47
|
+
rescue StandardError => e
|
48
|
+
Runtime::Logger.error("Failed to save test execution metrics, error: #{e}")
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
attr_reader :examples, :test_metrics, :influxdb_url, :influxdb_token, :influxdb_bucket, :run_type
|
54
|
+
|
55
|
+
# Transform example to influxdb compatible metrics data
|
56
|
+
# https://github.com/influxdata/influxdb-client-ruby#data-format
|
57
|
+
#
|
58
|
+
# @param [RSpec::Core::Example] example
|
59
|
+
# @param [Array<String>] custom_keys_tags
|
60
|
+
# @param [Array<String>] custom_keys_fields
|
61
|
+
# @return [Hash]
|
62
|
+
def parse_test_results(example, custom_keys_tags, custom_keys_fields)
|
63
|
+
{
|
64
|
+
name: 'test-stats',
|
65
|
+
time: time,
|
66
|
+
tags: tags(example, custom_keys_tags, run_type),
|
67
|
+
fields: fields(example, custom_keys_fields)
|
68
|
+
}
|
69
|
+
rescue StandardError => e
|
70
|
+
Runtime::Logger.error("Failed to transform example '#{example.id}', error: #{e}")
|
71
|
+
nil
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
metadata
CHANGED
@@ -1,15 +1,35 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gitlab_quality-test_tooling
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.24.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: 2024-04-
|
11
|
+
date: 2024-04-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '6.1'
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '7.2'
|
23
|
+
type: :development
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '6.1'
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '7.2'
|
13
33
|
- !ruby/object:Gem::Dependency
|
14
34
|
name: climate_control
|
15
35
|
requirement: !ruby/object:Gem::Requirement
|
@@ -66,6 +86,20 @@ dependencies:
|
|
66
86
|
- - "~>"
|
67
87
|
- !ruby/object:Gem::Version
|
68
88
|
version: '4.7'
|
89
|
+
- !ruby/object:Gem::Dependency
|
90
|
+
name: influxdb-client
|
91
|
+
requirement: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - "~>"
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '3.1'
|
96
|
+
type: :development
|
97
|
+
prerelease: false
|
98
|
+
version_requirements: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - "~>"
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '3.1'
|
69
103
|
- !ruby/object:Gem::Dependency
|
70
104
|
name: lefthook
|
71
105
|
requirement: !ruby/object:Gem::Requirement
|
@@ -404,6 +438,8 @@ files:
|
|
404
438
|
- lefthook.yml
|
405
439
|
- lib/gitlab_quality/test_tooling.rb
|
406
440
|
- lib/gitlab_quality/test_tooling/concerns/find_set_dri.rb
|
441
|
+
- lib/gitlab_quality/test_tooling/concerns/influxdb_tools.rb
|
442
|
+
- lib/gitlab_quality/test_tooling/concerns/test_metrics.rb
|
407
443
|
- lib/gitlab_quality/test_tooling/failed_jobs_table.rb
|
408
444
|
- lib/gitlab_quality/test_tooling/gitlab_client/branches_client.rb
|
409
445
|
- lib/gitlab_quality/test_tooling/gitlab_client/branches_dry_client.rb
|
@@ -461,6 +497,7 @@ files:
|
|
461
497
|
- lib/gitlab_quality/test_tooling/test_meta/processor/meta_processor.rb
|
462
498
|
- lib/gitlab_quality/test_tooling/test_meta/test_meta_updater.rb
|
463
499
|
- lib/gitlab_quality/test_tooling/test_metric/json_test_metric.rb
|
500
|
+
- lib/gitlab_quality/test_tooling/test_metric/log_test_metrics.rb
|
464
501
|
- lib/gitlab_quality/test_tooling/test_metrics/json_test_metric_collection.rb
|
465
502
|
- lib/gitlab_quality/test_tooling/test_result/base_test_result.rb
|
466
503
|
- lib/gitlab_quality/test_tooling/test_result/j_unit_test_result.rb
|
@@ -493,7 +530,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
493
530
|
- !ruby/object:Gem::Version
|
494
531
|
version: '0'
|
495
532
|
requirements: []
|
496
|
-
rubygems_version: 3.3.
|
533
|
+
rubygems_version: 3.3.27
|
497
534
|
signing_key:
|
498
535
|
specification_version: 4
|
499
536
|
summary: A collection of test-related tools.
|