gitlab_quality-test_tooling 1.23.0 → 1.24.0

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: c63a7a25d0e3a05f515954353898bb8d0dcb9e7a90634087e738f5cb16174c57
4
- data.tar.gz: 386833833dc0fd5cdc6334d3d613a38544fbc885c5ccb396070614f0e889555f
3
+ metadata.gz: '009e41374e42e637a571410da7cd95aa23ab4d5a6f1599f080a0ccea8432b820'
4
+ data.tar.gz: b81d4043e16bc5da888a102d3765250691acb705bb3edac3762c6baad0fe490f
5
5
  SHA512:
6
- metadata.gz: bb13a1c06a626b1152115262e0606480e78eb9aaafd6ed5e673cd72d6b1a268ab7659c020dbab743ebb872e71c1332905319d0ed6970cbde8bf0168c208fa2c3
7
- data.tar.gz: 40652c63561438f37f5610e5b22f93c6a0404c53d4dc65b17713bbf5469bf769dd13691361f02f5aa8229aab706f7e349db6abf663cf45745aaa040456461db7
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.23.0)
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.4
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, please refer to the [Flaky tests documentation](https://docs.gitlab.com/ee/development/testing_guide/flaky_tests.html)
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 1000..Float::INFINITY
142
+ when 42..Float::INFINITY
134
143
  '/label ~"flakiness::1"'
135
- when 500..999
144
+ when 22..41
136
145
  '/label ~"flakiness::2"'
137
- when 10..499
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module GitlabQuality
4
4
  module TestTooling
5
- VERSION = "1.23.0"
5
+ VERSION = "1.24.0"
6
6
  end
7
7
  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.23.0
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-15 00:00:00.000000000 Z
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.26
533
+ rubygems_version: 3.3.27
497
534
  signing_key:
498
535
  specification_version: 4
499
536
  summary: A collection of test-related tools.