gitlab_quality-test_tooling 3.13.0 → 3.14.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: e1284d833aad5c38a45cff835cf3e701193117aa9694293b42fee9ce60345b0d
4
- data.tar.gz: e209cf22987a7a19c58a4675ad529a6910ec387117a49fbcef97aadef9230c6f
3
+ metadata.gz: 473fb18a44195986ea17fec58d277b67fd7589fca55a08250704687e2b70aa80
4
+ data.tar.gz: 4c18c0a11106a2a6103c1e5ba139dd68d1879ccb94360bddd77e1cf3beeafd34
5
5
  SHA512:
6
- metadata.gz: '09ded437ab350eddf3736017e844865ef0a5ca311c6d62e800b87b272e691eab904ae12c6241f202f76ae1afa1e717880b99984e640fbd9728791e684fbfbbed'
7
- data.tar.gz: a9064f95ca94ef8d8d476f097f3ee9412d71dca931a715e9f446a81f8646e75e14ab141b93fe83ebd1bf898d32503b4806179ac2f9b3233fe69c95024c693491
6
+ metadata.gz: ba2aac53d5ce9f33bba7791ddc5dfaae10dff68f118d0de6ab33ca9569b64e1a555f29ee9bb9d3d1b04d9f182e374dac27a3d75bd561531ceaa6c728f0a63545
7
+ data.tar.gz: 999efaaa3f6067aeb29b7c0cb00bf3ba8877f61fb108f8ef4213b1946e739a22249b77cf0a54feed33396ca6d5677920a34070d5b861a49535070f1cbc4bf464
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- gitlab_quality-test_tooling (3.13.0)
4
+ gitlab_quality-test_tooling (3.14.0)
5
5
  activesupport (>= 7.0)
6
6
  amatch (~> 0.4.1)
7
7
  fog-google (~> 1.24, >= 1.24.1)
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "httparty"
4
+ require "json"
5
+
6
+ module GitlabQuality
7
+ module TestTooling
8
+ module TestMetricsExporter
9
+ class Client
10
+ ResponseError = Class.new(StandardError)
11
+
12
+ TESTS_PATH = "/api/v1/tests"
13
+ # Observer enforces a per-request cap; batch above this size to avoid silent failures.
14
+ MAX_BATCH_SIZE = 10_000
15
+
16
+ def initialize(url:, token:)
17
+ @url = url
18
+ @token = token
19
+ end
20
+
21
+ # POST array of test metric records to the observer service.
22
+ # Wraps each batch as { "tests" => [...] } and splits oversized payloads
23
+ # into chunks of at most MAX_BATCH_SIZE records.
24
+ #
25
+ # @param tests [Array<Hash>]
26
+ # @return [Boolean] true when every batch succeeds (or input is empty)
27
+ # @raise [ResponseError] on the first non-2xx batch response
28
+ def post_tests(tests)
29
+ tests.each_slice(MAX_BATCH_SIZE) do |batch|
30
+ response = HTTParty.post(
31
+ "#{url.to_s.chomp('/')}#{TESTS_PATH}",
32
+ body: { tests: batch }.to_json,
33
+ headers: {
34
+ "X-Gitlab-Token" => token,
35
+ "Content-Type" => "application/json"
36
+ }
37
+ )
38
+ raise ResponseError, "Observer request failed with status #{response.code}: #{response.body}" unless response.success?
39
+ end
40
+
41
+ true
42
+ end
43
+
44
+ private
45
+
46
+ attr_reader :url, :token
47
+ end
48
+ end
49
+ end
50
+ end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "logger"
3
4
  require "singleton"
4
5
 
5
6
  module GitlabQuality
@@ -18,13 +19,9 @@ module GitlabQuality
18
19
  end
19
20
  end
20
21
 
21
- attr_reader :initial_run
22
22
  attr_accessor :run_type,
23
- :clickhouse_url,
24
- :clickhouse_database,
25
- :clickhouse_table_name,
26
- :clickhouse_username,
27
- :clickhouse_password
23
+ :observer_url,
24
+ :observer_token
28
25
  attr_writer :extra_rspec_metadata_keys,
29
26
  :skip_record_proc,
30
27
  :test_retried_proc,
@@ -32,44 +29,13 @@ module GitlabQuality
32
29
  :spec_file_path_prefix,
33
30
  :logger
34
31
 
35
- # rubocop:disable Style/TrivialAccessors -- allows adding documentation for extra_metadata_columns
36
-
37
- # Additional columns to be created in the table if initial_run setup is used
38
- # Columns should be defined in the format used for ALTER TABLE query, example;
39
- # [
40
- # "feature_category LowCardinality(String) DEFAULT ''",
41
- # "level LowCardinality(String) DEFAULT ''"
42
- # ]
43
- #
44
- # @param columns [Array]
45
- # @return [Array]
46
- def extra_metadata_columns=(columns)
47
- @extra_metadata_columns = columns
48
- end
49
-
50
- # rubocop:enable Style/TrivialAccessors
51
-
52
- # Whether ClickHouse export is configured
32
+ # Whether observer export is configured
53
33
  #
54
34
  # Export is considered enabled when all required attributes are set
55
35
  #
56
36
  # @return [Boolean]
57
- def clickhouse_configured?
58
- [clickhouse_url, clickhouse_database, clickhouse_table_name, clickhouse_username, clickhouse_password].none?(&:blank?)
59
- end
60
-
61
- # Marks execution as initial run and performs setup tasks before running tests, like creating database in ClickHouse
62
- #
63
- # @return [Boolean]
64
- def initial_run!
65
- @initial_run = true
66
- end
67
-
68
- # Additional metadata columns used during initial table creation
69
- #
70
- # @return [Array]
71
- def extra_metadata_columns
72
- @extra_metadata_columns ||= []
37
+ def observer_configured?
38
+ [observer_url, observer_token].none? { |value| value.nil? || value.to_s.empty? }
73
39
  end
74
40
 
75
41
  # Extra rspec metadata keys to include in exported metrics
@@ -1,35 +1,37 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'logger'
3
4
  require 'active_support/core_ext/object/blank'
4
5
 
6
+ require_relative 'config'
7
+ require_relative 'formatter'
8
+
5
9
  module GitlabQuality
6
10
  module TestTooling
7
11
  module TestMetricsExporter
8
12
  class ConfigHelper
9
- REQUIRED_CLICKHOUSE_ENV_VARS = %w[
10
- GLCI_DA_CLICKHOUSE_URL
11
- GLCI_CLICKHOUSE_METRICS_USERNAME
12
- GLCI_CLICKHOUSE_METRICS_PASSWORD
13
- GLCI_CLICKHOUSE_METRICS_DB
14
- GLCI_CLICKHOUSE_METRICS_TABLE
15
- GLCI_CLICKHOUSE_SHARED_DB
13
+ REQUIRED_OBSERVER_ENV_VARS = %w[
14
+ GLCI_OBSERVER_URL
15
+ GLCI_OBSERVER_AUTH_TOKEN
16
16
  ].freeze
17
17
 
18
18
  class << self
19
19
  def configure!(run_type = test_run_type)
20
- return unless ENV.fetch("CI", nil) && ENV["GLCI_EXPORT_TEST_METRICS"] == "true" && run_type
20
+ return unless ENV.fetch("CI", nil) && ENV.fetch("GLCI_EXPORT_TEST_METRICS", "true") == "true" && run_type
21
21
 
22
22
  RSpec.configure do |rspec_config|
23
23
  next if rspec_config.dry_run?
24
24
 
25
25
  Config.configure do |exporter_config|
26
26
  self.logger = exporter_config.logger
27
- next warn_missing_clickhouse_variables unless clickhouse_env_vars_present?
27
+ next warn_missing_observer_variables unless observer_env_vars_present?
28
28
 
29
29
  yield(exporter_config) if block_given?
30
30
  configure_exporter!(exporter_config, run_type)
31
31
 
32
32
  rspec_config.add_formatter Formatter
33
+
34
+ logger.info("Test metrics export is enabled for run type: #{run_type}")
33
35
  end
34
36
  end
35
37
  end
@@ -42,59 +44,30 @@ module GitlabQuality
42
44
  @logger ||= Logger.new($stdout)
43
45
  end
44
46
 
45
- def clickhouse_env_vars_present?
46
- REQUIRED_CLICKHOUSE_ENV_VARS.all? { |var| ENV.fetch(var, nil) && !ENV[var].empty? }
47
- end
48
-
49
- def owner_records
50
- @owner_records ||= GitlabQuality::TestTooling::CodeCoverage::ClickHouse::CategoryOwnersTable.new(
51
- database: ENV.fetch("GLCI_CLICKHOUSE_SHARED_DB", nil),
52
- url: clickhouse_url,
53
- username: clickhouse_username,
54
- password: clickhouse_password
55
- ).owner_records
56
- rescue StandardError => e
57
- logger.error("Failed to retrieve owner data: #{e}")
58
- @owner_records = {}
47
+ def observer_env_vars_present?
48
+ REQUIRED_OBSERVER_ENV_VARS.all? { |var| ENV.fetch(var, nil) && !ENV[var].empty? }
59
49
  end
60
50
 
61
51
  def configure_exporter!(config, run_type)
62
52
  config.run_type = run_type
63
53
  config.custom_metrics_proc = custom_metrics_proc
64
54
 
65
- configure_clickhouse!(config)
55
+ configure_observer!(config)
66
56
  end
67
57
 
68
- def configure_clickhouse!(config)
69
- config.clickhouse_database = ENV.fetch("GLCI_CLICKHOUSE_METRICS_DB", nil)
70
- config.clickhouse_table_name = ENV.fetch("GLCI_CLICKHOUSE_METRICS_TABLE", nil)
71
- config.clickhouse_url = clickhouse_url
72
- config.clickhouse_username = clickhouse_username
73
- config.clickhouse_password = clickhouse_password
58
+ def configure_observer!(config)
59
+ config.observer_url = observer_url
60
+ config.observer_token = observer_token
74
61
  end
75
62
 
76
- def warn_missing_clickhouse_variables
77
- missing = REQUIRED_CLICKHOUSE_ENV_VARS.reject { |var| ENV.fetch(var, nil) && !ENV[var].empty? }
63
+ def warn_missing_observer_variables
64
+ missing = REQUIRED_OBSERVER_ENV_VARS.reject { |var| ENV.fetch(var, nil) && !ENV[var].empty? }
78
65
  logger.warn("Test metrics export is enabled but missing environment variables: #{missing.join(', ')}")
79
66
  end
80
67
 
81
68
  def custom_metrics_proc
82
- proc do |example|
83
- feature_category = example.metadata[:feature_category]
84
-
85
- owners = if feature_category.blank?
86
- logger.warn("Example '#{example.description}' is missing feature category metadata!")
87
- {}
88
- elsif unowned?(feature_category)
89
- # currently will default to shared or tooling
90
- { group: feature_category, stage: feature_category, section: feature_category }
91
- else
92
- owner_records.fetch(feature_category.to_s, {}).tap do |o|
93
- logger.warn("Feature category '#{feature_category}' has no owner data") if o.empty?
94
- end
95
- end
96
-
97
- { pipeline_type: pipeline_type, ci_pipeline_id: ci_pipeline_id, **owners }
69
+ proc do |_example|
70
+ { pipeline_type: pipeline_type, ci_pipeline_id: ci_pipeline_id }
98
71
  end
99
72
  end
100
73
 
@@ -120,26 +93,16 @@ module GitlabQuality
120
93
  end
121
94
  end
122
95
 
123
- def unowned?(feature_category)
124
- GitlabQuality::TestTooling::CodeCoverage::ClickHouse::CategoryOwnersTable::KNOWN_UNOWNED.include?(
125
- feature_category.to_s
126
- )
127
- end
128
-
129
96
  def test_run_type
130
97
  @run_type ||= ENV.fetch("GLCI_TEST_METRICS_RUN_TYPE", nil)
131
98
  end
132
99
 
133
- def clickhouse_url
134
- ENV.fetch("GLCI_DA_CLICKHOUSE_URL", nil)
135
- end
136
-
137
- def clickhouse_username
138
- ENV.fetch("GLCI_CLICKHOUSE_METRICS_USERNAME", nil)
100
+ def observer_url
101
+ ENV.fetch("GLCI_OBSERVER_URL", nil)
139
102
  end
140
103
 
141
- def clickhouse_password
142
- ENV.fetch("GLCI_CLICKHOUSE_METRICS_PASSWORD", nil)
104
+ def observer_token
105
+ ENV.fetch("GLCI_OBSERVER_AUTH_TOKEN", nil)
143
106
  end
144
107
 
145
108
  def ci_pipeline_id
@@ -2,27 +2,17 @@
2
2
 
3
3
  require "rspec/core/formatters/base_formatter"
4
4
 
5
+ require_relative "test_metrics"
6
+ require_relative "client"
7
+
5
8
  module GitlabQuality
6
9
  module TestTooling
7
10
  module TestMetricsExporter
8
11
  class Formatter < RSpec::Core::Formatters::BaseFormatter
9
- include Utils
10
-
11
- RSpec::Core::Formatters.register(self, :start, :stop)
12
+ RSpec::Core::Formatters.register(self, :stop)
12
13
 
13
14
  LOG_PREFIX = "[MetricsExporter]"
14
15
 
15
- def start(_notification)
16
- return unless config.initial_run
17
-
18
- logger.info("#{LOG_PREFIX} Running initial setup for metrics export")
19
- raise "Initial setup is enabled, but clickhouse configuration is missing!" unless config.clickhouse_configured?
20
-
21
- create_clickhouse_metrics_table
22
- rescue StandardError => e
23
- logger.error("#{LOG_PREFIX} Error occurred during initial setup: #{e.message}")
24
- end
25
-
26
16
  def stop(notification)
27
17
  logger.debug("#{LOG_PREFIX} Starting test metrics export")
28
18
  data = notification.examples.filter_map do |example|
@@ -32,32 +22,44 @@ module GitlabQuality
32
22
  end
33
23
  return logger.warn("#{LOG_PREFIX} No test execution records found, metrics will not be exported!") if data.empty?
34
24
 
35
- push_to_clickhouse(data)
25
+ push_to_observer(data)
36
26
  end
37
27
 
38
28
  private
39
29
 
30
+ def config
31
+ Config.configuration
32
+ end
33
+
34
+ def logger
35
+ config.logger
36
+ end
37
+
40
38
  # Single common timestamp for all exported example metrics to keep data points consistently grouped
41
39
  #
42
40
  # @return [String]
43
41
  def time
44
42
  return @time if @time
45
43
 
46
- ci_created_at = Runtime::Env.ci_pipeline_created_at
44
+ ci_created_at = ENV.fetch("CI_PIPELINE_CREATED_AT", nil)
47
45
  @time = (ci_created_at ? Time.strptime(ci_created_at, '%Y-%m-%dT%H:%M:%S%z') : Time.now.utc).strftime('%Y-%m-%dT%H:%M:%S.%6N')
48
46
  end
49
47
 
50
- # Push data to clickhouse
48
+ # Push data to observer service
51
49
  #
52
50
  # @param data [Array<Hash>]
53
51
  # @return [void]
54
- def push_to_clickhouse(data)
55
- return logger.debug("ClickHouse configuration missing, skipping ClickHouse export!") unless config.clickhouse_configured?
52
+ def push_to_observer(data)
53
+ return logger.debug("#{LOG_PREFIX} Observer configuration missing, skipping export!") unless config.observer_configured?
56
54
 
57
- clickhouse_client.insert_json_data(config.clickhouse_table_name, data)
58
- logger.info("#{LOG_PREFIX} Successfully pushed #{data.size} entries to ClickHouse!")
55
+ observer_client.post_tests(data)
56
+ logger.info("#{LOG_PREFIX} Successfully pushed #{data.size} entries to Observer!")
59
57
  rescue StandardError => e
60
- logger.error("#{LOG_PREFIX} Error occurred while pushing metrics to ClickHouse: #{e.message}")
58
+ logger.error("#{LOG_PREFIX} Error occurred while pushing metrics to Observer: #{e.message}")
59
+ end
60
+
61
+ def observer_client
62
+ Client.new(url: config.observer_url, token: config.observer_token)
61
63
  end
62
64
  end
63
65
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module GitlabQuality
4
4
  module TestTooling
5
- VERSION = "3.13.0"
5
+ VERSION = "3.14.0"
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: 3.13.0
4
+ version: 3.14.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: 2026-04-24 00:00:00.000000000 Z
11
+ date: 2026-05-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: climate_control
@@ -588,11 +588,11 @@ files:
588
588
  - lib/gitlab_quality/test_tooling/test_meta/test_meta_updater.rb
589
589
  - lib/gitlab_quality/test_tooling/test_metric/json_test_metric.rb
590
590
  - lib/gitlab_quality/test_tooling/test_metrics/json_test_metric_collection.rb
591
+ - lib/gitlab_quality/test_tooling/test_metrics_exporter/client.rb
591
592
  - lib/gitlab_quality/test_tooling/test_metrics_exporter/config.rb
592
593
  - lib/gitlab_quality/test_tooling/test_metrics_exporter/config_helper.rb
593
594
  - lib/gitlab_quality/test_tooling/test_metrics_exporter/formatter.rb
594
595
  - lib/gitlab_quality/test_tooling/test_metrics_exporter/test_metrics.rb
595
- - lib/gitlab_quality/test_tooling/test_metrics_exporter/utils.rb
596
596
  - lib/gitlab_quality/test_tooling/test_quarantine/quarantine_formatter.rb
597
597
  - lib/gitlab_quality/test_tooling/test_quarantine/quarantine_helper.rb
598
598
  - lib/gitlab_quality/test_tooling/test_result/base_test_result.rb
@@ -1,87 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module GitlabQuality
4
- module TestTooling
5
- module TestMetricsExporter
6
- module Utils
7
- # Instance of metrics exporter configuration
8
- #
9
- # @return [Config]
10
- def config
11
- Config.configuration
12
- end
13
-
14
- # Configured logger instance
15
- #
16
- # @return [Logger]
17
- def logger
18
- config.logger
19
- end
20
-
21
- # Configured clickhouse client
22
- #
23
- # @return [ClickHouse::Client]
24
- def clickhouse_client
25
- ClickHouse::Client.new(
26
- url: config.clickhouse_url,
27
- database: config.clickhouse_database,
28
- username: config.clickhouse_username,
29
- password: config.clickhouse_password,
30
- logger: logger
31
- )
32
- end
33
-
34
- # Create table for metrics export using current ClickHouse configuration
35
- #
36
- # This method is mostly for schema documentation but it can be used together with initial_run! method in
37
- #
38
- # @return [void]
39
- def create_clickhouse_metrics_table
40
- table_name = config.clickhouse_table_name
41
-
42
- clickhouse_client.query(<<~SQL)
43
- CREATE TABLE IF NOT EXISTS #{table_name}
44
- (
45
- timestamp DateTime64(6, 'UTC'),
46
- id String,
47
- name String,
48
- hash String,
49
- file_path String,
50
- status LowCardinality(String),
51
- run_time UInt32,
52
- location String,
53
- quarantined Bool,
54
- test_retried Bool,
55
- feature_category LowCardinality(String) DEFAULT 'unknown',
56
- run_type LowCardinality(String) DEFAULT 'unknown',
57
- spec_file_path_prefix LowCardinality(String) DEFAULT '',
58
- ci_project_id UInt32,
59
- ci_job_name LowCardinality(String),
60
- ci_job_id UInt64,
61
- ci_pipeline_id UInt64,
62
- ci_merge_request_iid UInt32 DEFAULT 0,
63
- ci_project_path LowCardinality(String),
64
- ci_branch String,
65
- ci_target_branch LowCardinality(String),
66
- ci_server_url LowCardinality(String) DEFAULT 'https://gitlab.com',
67
- exception_class String DEFAULT '',
68
- exception_classes Array(String) DEFAULT [],
69
- failure_exception String DEFAULT ''
70
- )
71
- ENGINE = MergeTree()
72
- PARTITION BY toYYYYMM(timestamp)
73
- ORDER BY (ci_project_path, status, run_type, feature_category, file_path, timestamp, ci_pipeline_id)
74
- SETTINGS index_granularity = 8192;
75
- SQL
76
- return if config.extra_metadata_columns.empty?
77
-
78
- clickhouse_client.query(
79
- "ALTER TABLE #{table_name} #{config.extra_metadata_columns.map { |column| "ADD COLUMN IF NOT EXISTS #{column}" }.join(', ')};"
80
- )
81
- end
82
-
83
- module_function :config, :logger, :clickhouse_client
84
- end
85
- end
86
- end
87
- end