gitlab_quality-test_tooling 3.18.3 → 3.19.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: f524117673a4935ef736ec70c0fbbdaef37e47ce9f6745560b39d27498b3d6dc
4
- data.tar.gz: b576628aa613113b8969659d2e3f1e3267eb0ea8af12871fc062c9dd5c738b1a
3
+ metadata.gz: de285d350eb535db983a229c6fbe2ea8565fd92ac757ba4371a7abf2e86a0e44
4
+ data.tar.gz: bb1143033fdd4d8e0c71c4a0ca27574391138feed3b5198f934df1356684c866
5
5
  SHA512:
6
- metadata.gz: c01d93893558f7f7cdbfcc96787d8367583f83f66527799cf161def8f9d1d2444f7bd31f65b96926f7610b9992f95b676a34471be03cc3964ea3b8935fd358f0
7
- data.tar.gz: d830ae9098aa8c636390350d6c12c706545b4a35bcd4022d1c194515b9a7c9f8d08dfa3079e2abb461e7647f521200acd951da496073f744ebd6cec459070620
6
+ metadata.gz: 43d770b36e5b946f287a5f309f0e242033432c9b9a2f7618e396abc894067314d26a0b88416c41f880d1a16c5ffe7a595e306f074a057478fb139f2361e77e56
7
+ data.tar.gz: 8f61352e3441e40c2b09d45621e91028f8d1f48623859ba34f5aabc7db449b03bc80f18ede9cbb558c47776d4a6b10c7fdb8e39e22b1dd0158477f2d85d97d8c
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- gitlab_quality-test_tooling (3.18.3)
4
+ gitlab_quality-test_tooling (3.19.0)
5
5
  activesupport (>= 7.0)
6
6
  amatch (~> 0.4.1)
7
7
  fog-google (~> 1.24, >= 1.24.1)
data/exe/test-coverage CHANGED
@@ -14,6 +14,7 @@ require_relative '../lib/gitlab_quality/test_tooling/code_coverage/click_house/p
14
14
  require_relative '../lib/gitlab_quality/test_tooling/code_coverage/click_house/test_file_mappings_table'
15
15
  require_relative '../lib/gitlab_quality/test_tooling/code_coverage/click_house/jest_quarantined_tests_table'
16
16
  require_relative '../lib/gitlab_quality/test_tooling/code_coverage/click_house/test_health_risk_aggregator'
17
+ require_relative '../lib/gitlab_quality/test_tooling/code_coverage/click_house/test_health_score_snapshotter'
17
18
  require_relative '../lib/gitlab_quality/test_tooling/code_coverage/coverage_data'
18
19
  require_relative '../lib/gitlab_quality/test_tooling/code_coverage/lcov_file'
19
20
  require_relative '../lib/gitlab_quality/test_tooling/code_coverage/artifacts'
@@ -177,6 +178,13 @@ clickhouse_data = {
177
178
  case mode
178
179
  when :aggregation
179
180
  GitlabQuality::TestTooling::CodeCoverage::ClickHouse::TestHealthRiskAggregator.new(**clickhouse_data).run
181
+ # Best-effort: the score snapshot is a non-critical add-on, so it must not
182
+ # fail the job after the aggregation has already succeeded.
183
+ begin
184
+ GitlabQuality::TestTooling::CodeCoverage::ClickHouse::TestHealthScoreSnapshotter.new(**clickhouse_data).run
185
+ rescue StandardError => e
186
+ warn "Warning: score snapshot failed (aggregation succeeded), continuing: #{e.message}"
187
+ end
180
188
  when :per_test
181
189
  GitlabQuality::TestTooling::CodeCoverage::PerTestCoverageExporter.new(
182
190
  coverage_glob: params[:per_test_coverage],
@@ -71,13 +71,6 @@ WITH
71
71
  GROUP BY test_file
72
72
  HAVING is_flaky
73
73
  ),
74
- -- Jest quarantine comes from a flat file (quarantined_vue3_specs.txt) rather
75
- -- than the issue-based RSpec ledger. It is populated into this helper table
76
- -- by JestQuarantinedTestsTable before each nightly aggregation run.
77
- jest_quarantine_status AS (
78
- SELECT test_file
79
- FROM code_coverage.jest_quarantined_tests_today FINAL
80
- ),
81
74
  per_test_status AS (
82
75
  SELECT
83
76
  tc.source_file,
@@ -86,12 +79,14 @@ WITH
86
79
  tc.section,
87
80
  tc.total_lines,
88
81
  tc.covered_lines,
89
- (coalesce(qs.is_quarantined, FALSE) OR (jq.test_file IS NOT NULL)) AS is_quarantined,
82
+ -- Jest quarantine is a flat list (quarantined_vue3_specs.txt), loaded into
83
+ -- jest_quarantined_tests_today by JestQuarantinedTestsTable before each run.
84
+ (coalesce(qs.is_quarantined, FALSE)
85
+ OR tc.test_file IN (SELECT test_file FROM code_coverage.jest_quarantined_tests_today FINAL)) AS is_quarantined,
90
86
  coalesce(fs.is_flaky, FALSE) AS is_flaky
91
87
  FROM code_coverage.test_coverage_per_file tc FINAL
92
- LEFT JOIN quarantine_status qs ON qs.test_file = tc.test_file
93
- LEFT JOIN jest_quarantine_status jq ON jq.test_file = tc.test_file
94
- LEFT JOIN flaky_status fs ON fs.test_file = tc.test_file
88
+ LEFT JOIN quarantine_status qs ON qs.test_file = tc.test_file
89
+ LEFT JOIN flaky_status fs ON fs.test_file = tc.test_file
95
90
  WHERE tc.timestamp >= now() - INTERVAL {coverage_window}
96
91
  ),
97
92
  per_file AS (
@@ -0,0 +1,31 @@
1
+ -- Freezes today's per-pillar Test Health Index scores into the history table.
2
+ --
3
+ -- Reads one pillar's score view (the same view the dashboard reads, so the
4
+ -- snapshot and the dashboard cannot diverge) and tags each row with the
5
+ -- snapshot date and the pillar. Run once per pillar by
6
+ -- TestHealthScoreSnapshotter, right after the daily risk aggregation refreshes
7
+ -- test_health_risk_per_group, which the view reads.
8
+ --
9
+ -- Idempotency: the target is a SharedReplacingMergeTree keyed by
10
+ -- (snapshot_date, level, group, stage, section, pillar) with a version column,
11
+ -- so re-running for the same snapshot_date upserts and the last run of the day
12
+ -- wins on merge. No "final run of the day" detection is needed.
13
+ --
14
+ -- Parameter substitution uses {name} braces, replaced via gsub in
15
+ -- TestHealthScoreSnapshotter#build_sql after each value passes a regex check:
16
+ -- {snapshot_date} : the date stamp for this run, e.g. '2026-05-07'
17
+ -- {pillar} : quarantine | slow (names the view and tags the rows)
18
+ INSERT INTO code_coverage.test_health_score_history
19
+ (snapshot_date, level, `group`, stage, section, pillar, score, rag_status, formula_version, factors)
20
+ SELECT
21
+ toDate('{snapshot_date}') AS snapshot_date,
22
+ level,
23
+ `group`,
24
+ stage,
25
+ section,
26
+ '{pillar}' AS pillar,
27
+ score,
28
+ rag_status,
29
+ formula_version,
30
+ factors
31
+ FROM code_coverage.test_health_scores_{pillar};
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'client'
4
+ require_relative 'table'
5
+
6
+ module GitlabQuality
7
+ module TestTooling
8
+ module CodeCoverage
9
+ module ClickHouse
10
+ # Freezes the day's per-pillar Test Health Index scores into
11
+ # `code_coverage.test_health_score_history`. Reads the per-pillar score
12
+ # views (the same views the dashboard reads, so the snapshot cannot
13
+ # diverge from the live score) and tags each row with the snapshot date
14
+ # and the pillar.
15
+ #
16
+ # Runs right after TestHealthRiskAggregator in the daily job: the views
17
+ # read `test_health_risk_per_group`, which the aggregator has just
18
+ # refreshed, so the freeze captures the current day. The producer never
19
+ # holds the score formula; it copies whatever the views compute.
20
+ class TestHealthScoreSnapshotter
21
+ include Client
22
+
23
+ SQL_FILE = File.expand_path('test_health_score_snapshot.sql', __dir__)
24
+
25
+ # One INSERT per pillar. The token names the view
26
+ # (test_health_scores_<pillar>) and tags the row's `pillar` column.
27
+ # Flaky is excluded until it is scored live.
28
+ PILLARS = %w[quarantine slow].freeze
29
+
30
+ DATE_PATTERN = /\A\d{4}-\d{2}-\d{2}\z/
31
+
32
+ def initialize(url:, database:, username: nil, password: nil, logger: nil)
33
+ @url = url
34
+ @database = database
35
+ @username = username
36
+ @password = password
37
+ @logger = logger || ::Logger.new($stdout, level: ::Logger::INFO)
38
+ end
39
+
40
+ # @param snapshot_date [Date, String] date stamp for this run; defaults to today.
41
+ # @return [void]
42
+ def run(snapshot_date: Date.today)
43
+ PILLARS.each do |pillar|
44
+ logger.info("#{LOG_PREFIX} Snapshotting #{pillar} scores snapshot_date=#{snapshot_date}")
45
+ client.query(build_sql(snapshot_date: snapshot_date, pillar: pillar), format: "TabSeparated")
46
+ end
47
+ inserted = fetch_row_count(snapshot_date)
48
+ logger.info("#{LOG_PREFIX} Score snapshot wrote #{inserted} rows for snapshot_date=#{snapshot_date}")
49
+ rescue StandardError => e
50
+ logger.error("#{LOG_PREFIX} Score snapshot failed for #{snapshot_date}: #{e.message}")
51
+ raise
52
+ end
53
+
54
+ private
55
+
56
+ attr_reader :url, :database, :username, :password, :logger
57
+
58
+ def build_sql(snapshot_date:, pillar:)
59
+ File.read(SQL_FILE)
60
+ .gsub('{snapshot_date}', validate_date(snapshot_date))
61
+ .gsub('{pillar}', validate_pillar(pillar))
62
+ end
63
+
64
+ # DateTime/Time `to_s` includes the time portion and is rejected.
65
+ def validate_date(value)
66
+ str = value.to_s
67
+ raise ArgumentError, "Invalid snapshot_date: #{value.inspect}" unless DATE_PATTERN.match?(str)
68
+
69
+ str
70
+ end
71
+
72
+ # The pillar names the view and is interpolated into the SQL, so
73
+ # restrict it to the known set rather than a looser pattern.
74
+ def validate_pillar(value)
75
+ raise ArgumentError, "Invalid pillar: #{value.inspect}" unless PILLARS.include?(value)
76
+
77
+ value
78
+ end
79
+
80
+ # Returns 'unknown' on any error so a transient count-query failure
81
+ # can't mask the success of the actual INSERTs.
82
+ def fetch_row_count(snapshot_date)
83
+ count_sql = "SELECT count() FROM code_coverage.test_health_score_history FINAL " \
84
+ "WHERE snapshot_date = toDate('#{validate_date(snapshot_date)}')"
85
+ result = client.query(count_sql, format: "JSONCompact")
86
+ result&.dig('data', 0, 0) || 'unknown'
87
+ rescue StandardError => e
88
+ logger.debug("#{LOG_PREFIX} Could not fetch post-snapshot row count: #{e.message}")
89
+ 'unknown'
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -52,7 +52,10 @@ module GitlabQuality
52
52
 
53
53
  insert(coverage_files)
54
54
  jest_quarantine_error = ingest_jest_quarantine_best_effort
55
- aggregate unless skip_aggregation
55
+ unless skip_aggregation
56
+ aggregate
57
+ snapshot_scores
58
+ end
56
59
 
57
60
  # Re-raise only after aggregation has run, so a jest quarantine ingest
58
61
  # failure can't silently stop the table-wide rollup while still
@@ -112,6 +115,21 @@ module GitlabQuality
112
115
  ClickHouse::TestHealthRiskAggregator.new(**clickhouse_credentials).run
113
116
  end
114
117
 
118
+ # Freezes the day's per-pillar scores into test_health_score_history,
119
+ # right after the aggregation the score views read. Best-effort: the
120
+ # snapshot is a non-critical add-on, so a failure (for example before
121
+ # the score views exist) must not fail the daily job once the
122
+ # aggregation has succeeded. A missed freeze is recoverable because the
123
+ # dashboard reads the live view for today.
124
+ def snapshot_scores
125
+ ClickHouse::TestHealthScoreSnapshotter.new(**clickhouse_credentials).run
126
+ rescue StandardError => e
127
+ logger.error(
128
+ "#{ClickHouse::LOG_PREFIX} Score snapshot failed; the aggregation still " \
129
+ "succeeded, continuing: #{e.message}"
130
+ )
131
+ end
132
+
115
133
  # `tests_to_categories` comes from the test report JSON; without it, rows
116
134
  # carry blank categories. `CategoryOwners` fetches stages.yml over HTTP,
117
135
  # so only build it when there are reports to enrich.
@@ -2,6 +2,6 @@
2
2
 
3
3
  module GitlabQuality
4
4
  module TestTooling
5
- VERSION = "3.18.3"
5
+ VERSION = "3.19.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.18.3
4
+ version: 3.19.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-06-24 00:00:00.000000000 Z
11
+ date: 2026-06-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: climate_control
@@ -499,6 +499,8 @@ files:
499
499
  - lib/gitlab_quality/test_tooling/code_coverage/click_house/test_file_mappings_table.rb
500
500
  - lib/gitlab_quality/test_tooling/code_coverage/click_house/test_health_risk_aggregation.sql
501
501
  - lib/gitlab_quality/test_tooling/code_coverage/click_house/test_health_risk_aggregator.rb
502
+ - lib/gitlab_quality/test_tooling/code_coverage/click_house/test_health_score_snapshot.sql
503
+ - lib/gitlab_quality/test_tooling/code_coverage/click_house/test_health_score_snapshotter.rb
502
504
  - lib/gitlab_quality/test_tooling/code_coverage/coverage_data.rb
503
505
  - lib/gitlab_quality/test_tooling/code_coverage/lcov_file.rb
504
506
  - lib/gitlab_quality/test_tooling/code_coverage/per_test_coverage_data.rb