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