gitlab_quality-test_tooling 2.20.0 → 2.20.2
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/.ruby-version +1 -1
- data/.tool-versions +1 -1
- data/Gemfile.lock +30 -28
- data/exe/epic-readiness-notification +58 -0
- data/lib/gitlab_quality/test_tooling/click_house/client.rb +85 -0
- data/lib/gitlab_quality/test_tooling/feature_readiness/concerns/issue_concern.rb +1 -1
- data/lib/gitlab_quality/test_tooling/feature_readiness/concerns/work_item_concern.rb +11 -0
- data/lib/gitlab_quality/test_tooling/feature_readiness/epic_readiness_notifier.rb +308 -0
- data/lib/gitlab_quality/test_tooling/gcs_tools.rb +49 -0
- data/lib/gitlab_quality/test_tooling/gitlab_client/gitlab_client.rb +2 -9
- data/lib/gitlab_quality/test_tooling/gitlab_client/group_labels_client.rb +34 -0
- data/lib/gitlab_quality/test_tooling/gitlab_client/issues_client.rb +1 -1
- data/lib/gitlab_quality/test_tooling/report/generate_test_session.rb +1 -1
- data/lib/gitlab_quality/test_tooling/report/health_problem_reporter.rb +2 -3
- data/lib/gitlab_quality/test_tooling/report/merge_request_slow_tests_report.rb +2 -6
- data/lib/gitlab_quality/test_tooling/runtime/env.rb +8 -4
- data/lib/gitlab_quality/test_tooling/system_logs/finders/rails/api_log_finder.rb +1 -1
- data/lib/gitlab_quality/test_tooling/system_logs/finders/rails/application_log_finder.rb +1 -1
- data/lib/gitlab_quality/test_tooling/system_logs/finders/rails/exception_log_finder.rb +1 -1
- data/lib/gitlab_quality/test_tooling/system_logs/finders/rails/graphql_log_finder.rb +1 -1
- data/lib/gitlab_quality/test_tooling/test_meta/test_meta_updater.rb +1 -3
- data/lib/gitlab_quality/test_tooling/test_metrics_exporter/config.rb +88 -15
- data/lib/gitlab_quality/test_tooling/test_metrics_exporter/formatter.rb +71 -34
- data/lib/gitlab_quality/test_tooling/test_metrics_exporter/test_metrics.rb +105 -80
- data/lib/gitlab_quality/test_tooling/version.rb +1 -1
- metadata +58 -55
- data/lib/gitlab_quality/test_tooling/test_metrics_exporter/log_test_metrics.rb +0 -117
- data/lib/gitlab_quality/test_tooling/test_metrics_exporter/support/gcs_tools.rb +0 -49
- data/lib/gitlab_quality/test_tooling/test_metrics_exporter/support/influxdb_tools.rb +0 -33
@@ -43,14 +43,7 @@ module GitlabQuality
|
|
43
43
|
return unless ENV['CI_SLACK_WEBHOOK_URL']
|
44
44
|
|
45
45
|
pipeline = Runtime::Env.pipeline_from_project_name
|
46
|
-
channel =
|
47
|
-
when "canary"
|
48
|
-
"e2e-run-production"
|
49
|
-
when "staging", "staging-canary"
|
50
|
-
"e2e-run-staging"
|
51
|
-
else
|
52
|
-
"e2e-run-#{pipeline}"
|
53
|
-
end
|
46
|
+
channel = Runtime::Env.slack_alerts_channel
|
54
47
|
|
55
48
|
slack_options = {
|
56
49
|
slack_webhook_url: ENV.fetch('CI_SLACK_WEBHOOK_URL', nil),
|
@@ -58,7 +51,7 @@ module GitlabQuality
|
|
58
51
|
username: "GitLab Quality Test Tooling",
|
59
52
|
icon_emoji: ':ci_failing:',
|
60
53
|
message: <<~MSG
|
61
|
-
An unexpected error occurred while reporting test results in issues.
|
54
|
+
Env: #{pipeline}. An unexpected error occurred while reporting test results in issues.
|
62
55
|
The error occurred in job: #{Runtime::Env.ci_job_url}
|
63
56
|
`#{error.class.name} #{error.message}`
|
64
57
|
MSG
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GitlabQuality
|
4
|
+
module TestTooling
|
5
|
+
module GitlabClient
|
6
|
+
class GroupLabelsClient < GitlabClient
|
7
|
+
def initialize(token:, group:, endpoint: nil, **_kwargs)
|
8
|
+
@token = token
|
9
|
+
@group = group
|
10
|
+
@endpoint = endpoint
|
11
|
+
end
|
12
|
+
|
13
|
+
def group_labels(options: {})
|
14
|
+
client.group_labels(group, options)
|
15
|
+
end
|
16
|
+
|
17
|
+
def create_group_label(name:, color: '#428BCA', description: nil)
|
18
|
+
client.create_group_label(group, name, color, description: description)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
attr_reader :group, :token, :endpoint
|
24
|
+
|
25
|
+
def client
|
26
|
+
@client ||= Gitlab.client(
|
27
|
+
endpoint: endpoint || ENV['GITLAB_API_BASE'] || Runtime::Env.gitlab_api_base,
|
28
|
+
private_token: token
|
29
|
+
)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -68,7 +68,7 @@ module GitlabQuality
|
|
68
68
|
|
69
69
|
def find_issue_notes(iid:)
|
70
70
|
handle_gitlab_client_exceptions do
|
71
|
-
client.issue_notes(project, iid, order_by: 'created_at', sort: 'asc').auto_paginate
|
71
|
+
client.issue_notes(project, iid, order_by: 'created_at', sort: 'asc', activity_filter: 'only_comments').auto_paginate
|
72
72
|
end
|
73
73
|
end
|
74
74
|
|
@@ -31,7 +31,7 @@ module GitlabQuality
|
|
31
31
|
tests = tests.select { |test| pipeline_stages.include? test.report["stage"] } unless pipeline_stages.empty?
|
32
32
|
|
33
33
|
issue = gitlab.create_issue(
|
34
|
-
title: "#{Time.now.
|
34
|
+
title: "#{Time.now.to_date.iso8601} Test session report | #{Runtime::Env.qa_run_type}",
|
35
35
|
description: generate_description(tests),
|
36
36
|
labels: ['automation:bot-authored', 'E2E', 'triage report', pipeline_name_label, 'suppress-contributor-links'],
|
37
37
|
confidential: confidential
|
@@ -15,7 +15,6 @@ module GitlabQuality
|
|
15
15
|
class HealthProblemReporter < ReportAsIssue
|
16
16
|
include Concerns::GroupAndCategoryLabels
|
17
17
|
include Concerns::IssueReports
|
18
|
-
include TestMetricsExporter::Support::GcsTools
|
19
18
|
|
20
19
|
BASE_SEARCH_LABELS = ['test'].freeze
|
21
20
|
FOUND_IN_MR_LABEL = '~"found:in MR"'
|
@@ -151,7 +150,7 @@ module GitlabQuality
|
|
151
150
|
def push_test_to_gcs(tests_data, test_results_filename)
|
152
151
|
Runtime::Logger.info "will push the test data to GCS"
|
153
152
|
|
154
|
-
gcs_client(project_id: gcs_project_id, credentials: gcs_credentials, dry_run: dry_run).put_object(
|
153
|
+
GcsTools.gcs_client(project_id: gcs_project_id, credentials: gcs_credentials, dry_run: dry_run).put_object(
|
155
154
|
gcs_bucket,
|
156
155
|
gcs_metrics_file_name(test_results_filename),
|
157
156
|
tests_data.to_json,
|
@@ -163,7 +162,7 @@ module GitlabQuality
|
|
163
162
|
end
|
164
163
|
|
165
164
|
def gcs_metrics_file_name(test_results_filename)
|
166
|
-
today = Time.now.
|
165
|
+
today = Time.now.to_date.iso8601
|
167
166
|
|
168
167
|
"#{today}-#{test_results_filename}"
|
169
168
|
end
|
@@ -78,13 +78,9 @@ module GitlabQuality
|
|
78
78
|
end
|
79
79
|
|
80
80
|
def slow_test_rows(slow_test)
|
81
|
-
|
82
|
-
|
83
|
-
slow_test.each do |test|
|
84
|
-
rows << slow_test_table_row(test)
|
81
|
+
slow_test.map do |test|
|
82
|
+
slow_test_table_row(test)
|
85
83
|
end
|
86
|
-
|
87
|
-
rows
|
88
84
|
end
|
89
85
|
|
90
86
|
def build_note(slow_test)
|
@@ -22,10 +22,10 @@ module GitlabQuality
|
|
22
22
|
'CI_PROJECT_ID' => :ci_project_id,
|
23
23
|
'CI_PROJECT_NAME' => :ci_project_name,
|
24
24
|
'CI_PROJECT_PATH' => :ci_project_path,
|
25
|
+
'CI_PIPELINE_CREATED_AT' => :ci_pipeline_created_at,
|
25
26
|
'DEPLOY_VERSION' => :deploy_version,
|
26
27
|
'GITLAB_QA_ISSUE_URL' => :qa_issue_url,
|
27
|
-
'QA_GITLAB_CI_TOKEN' => :gitlab_ci_token
|
28
|
-
'SLACK_QA_CHANNEL' => :slack_qa_channel
|
28
|
+
'QA_GITLAB_CI_TOKEN' => :gitlab_ci_token
|
29
29
|
}.freeze
|
30
30
|
|
31
31
|
ENV_VARIABLES.each do |env_name, method_name|
|
@@ -62,6 +62,10 @@ module GitlabQuality
|
|
62
62
|
env_var_value_if_defined('GITLAB_GRAPHQL_API_BASE')
|
63
63
|
end
|
64
64
|
|
65
|
+
def slack_alerts_channel
|
66
|
+
env_var_value_if_defined('SLACK_ALERTS_CHANNEL') || 'C09HQ5BN07J' # test-tooling-alerts channel ID
|
67
|
+
end
|
68
|
+
|
65
69
|
def pipeline_from_project_name
|
66
70
|
%w[gitlab gitaly].any? { |str| ci_project_name.to_s.start_with?(str) } ? default_branch : ci_project_name
|
67
71
|
end
|
@@ -112,12 +116,12 @@ module GitlabQuality
|
|
112
116
|
end
|
113
117
|
|
114
118
|
def env_var_value_if_defined(variable)
|
115
|
-
|
119
|
+
ENV.fetch(variable) if env_var_value_valid?(variable)
|
116
120
|
end
|
117
121
|
|
118
122
|
def env_var_name_if_defined(variable)
|
119
123
|
# Pass through the variables if they are defined and not empty in the environment
|
120
|
-
|
124
|
+
"$#{variable}" if env_var_value_valid?(variable)
|
121
125
|
end
|
122
126
|
end
|
123
127
|
end
|
@@ -10,8 +10,6 @@ module GitlabQuality
|
|
10
10
|
|
11
11
|
attr_reader :project, :ref, :report_issue, :processed_commits, :token, :specs_file, :dry_run, :processor
|
12
12
|
|
13
|
-
TEST_PLATFORM_MAINTAINERS_SLACK_CHANNEL_ID = 'C0437FV9KBN' # test-platform-maintainers
|
14
|
-
|
15
13
|
def initialize(token:, project:, specs_file:, processor:, ref: 'master', dry_run: false)
|
16
14
|
@specs_file = specs_file
|
17
15
|
@token = token
|
@@ -294,7 +292,7 @@ module GitlabQuality
|
|
294
292
|
# @param [String] message the message to post
|
295
293
|
# @return [HTTP::Response]
|
296
294
|
def post_message_on_slack(message)
|
297
|
-
channel =
|
295
|
+
channel = Runtime::Env.slack_alerts_channel
|
298
296
|
slack_options = {
|
299
297
|
slack_webhook_url: ENV.fetch('CI_SLACK_WEBHOOK_URL', nil),
|
300
298
|
channel: channel,
|
@@ -8,6 +8,34 @@ module GitlabQuality
|
|
8
8
|
class Config
|
9
9
|
include Singleton
|
10
10
|
|
11
|
+
# GCS client configuration object used to push metrics as json file to gcs bucket
|
12
|
+
#
|
13
|
+
class GCS
|
14
|
+
def initialize(project_id:, credentials:, bucket_name:, metrics_file_name:, dry_run: false)
|
15
|
+
@project_id = project_id
|
16
|
+
@credentials = credentials
|
17
|
+
@bucket_name = bucket_name
|
18
|
+
@metrics_file_name = metrics_file_name
|
19
|
+
@dry_run = dry_run
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_reader :project_id, :credentials, :bucket_name, :metrics_file_name, :dry_run
|
23
|
+
end
|
24
|
+
|
25
|
+
# ClickHouse client configuration object
|
26
|
+
#
|
27
|
+
class ClickHouse
|
28
|
+
def initialize(url:, database:, table_name:, username:, password:)
|
29
|
+
@url = url
|
30
|
+
@database = database
|
31
|
+
@table_name = table_name
|
32
|
+
@username = username
|
33
|
+
@password = password
|
34
|
+
end
|
35
|
+
|
36
|
+
attr_reader :url, :database, :table_name, :username, :password
|
37
|
+
end
|
38
|
+
|
11
39
|
class << self
|
12
40
|
def configuration
|
13
41
|
Config.instance
|
@@ -18,25 +46,70 @@ module GitlabQuality
|
|
18
46
|
end
|
19
47
|
end
|
20
48
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
:
|
25
|
-
:
|
26
|
-
:
|
27
|
-
:
|
28
|
-
:test_metric_file_name,
|
29
|
-
:run_type
|
49
|
+
attr_reader :gcs_config, :clickhouse_config
|
50
|
+
attr_accessor :run_type
|
51
|
+
attr_writer :extra_rspec_metadata_keys,
|
52
|
+
:skip_record_proc,
|
53
|
+
:test_retried_proc,
|
54
|
+
:custom_metrics_proc,
|
55
|
+
:logger
|
30
56
|
|
31
|
-
|
32
|
-
|
57
|
+
# rubocop:disable Style/TrivialAccessors -- allows documenting that setting config enables the export as well as document input class type
|
58
|
+
|
59
|
+
# Enable metrics export to gcs bucket by setting configuration object
|
60
|
+
#
|
61
|
+
# @param config [Config::GCS]
|
62
|
+
# @return [GCS]
|
63
|
+
def gcs_config=(config)
|
64
|
+
@gcs_config = config
|
65
|
+
end
|
66
|
+
|
67
|
+
# Enable metrics export to clickhouse by setting configuration object
|
68
|
+
#
|
69
|
+
# @param config [Config::ClickHouse]
|
70
|
+
# @return [ClickHouse]
|
71
|
+
def clickhouse_config=(config)
|
72
|
+
@clickhouse_config = config
|
73
|
+
end
|
74
|
+
|
75
|
+
# rubocop:enable Style/TrivialAccessors
|
76
|
+
|
77
|
+
# Extra rspec metadata keys to include in exported metrics
|
78
|
+
#
|
79
|
+
# @return [Array<Symbol>]
|
80
|
+
def extra_rspec_metadata_keys
|
81
|
+
@extra_rspec_metadata_keys ||= []
|
82
|
+
end
|
83
|
+
|
84
|
+
# A lambda that determines whether to skip recording a test result
|
85
|
+
#
|
86
|
+
# This is useful when you would want to skip initial failure when retrying specs is set up in a separate process
|
87
|
+
# and you want to avoid duplicate records
|
88
|
+
#
|
89
|
+
# @return [Proc]
|
90
|
+
def skip_record_proc
|
91
|
+
@skip_record_proc ||= ->(_example) { false }
|
92
|
+
end
|
93
|
+
|
94
|
+
# A lambda that determines whether a test was retried or not
|
95
|
+
#
|
96
|
+
# @return [Proc]
|
97
|
+
def test_retried_proc
|
98
|
+
@test_retried_proc ||= ->(_example) { false }
|
99
|
+
end
|
33
100
|
|
34
|
-
|
35
|
-
|
101
|
+
# A lambda that return hash with additional custom metrics
|
102
|
+
#
|
103
|
+
# @return [Proc]
|
104
|
+
def custom_metrics_proc
|
105
|
+
@custom_metrics_proc ||= ->(_example) { {} }
|
36
106
|
end
|
37
107
|
|
38
|
-
|
39
|
-
|
108
|
+
# Logger instance
|
109
|
+
#
|
110
|
+
# @return [Logger]
|
111
|
+
def logger
|
112
|
+
@logger ||= Logger.new($stdout, level: Logger::INFO)
|
40
113
|
end
|
41
114
|
end
|
42
115
|
end
|
@@ -1,56 +1,93 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "rspec/core/formatters/base_formatter"
|
4
|
+
|
5
|
+
# rubocop:disable Metrics/AbcSize
|
3
6
|
module GitlabQuality
|
4
7
|
module TestTooling
|
5
8
|
module TestMetricsExporter
|
6
9
|
class Formatter < RSpec::Core::Formatters::BaseFormatter
|
7
10
|
RSpec::Core::Formatters.register(self, :stop)
|
8
11
|
|
12
|
+
LOG_PREFIX = "[MetricsExporter]"
|
13
|
+
|
9
14
|
def stop(notification)
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
)
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
custom_keys_tags: config.custom_keys_tags,
|
20
|
-
custom_keys_fields: config.custom_keys_fields
|
21
|
-
)
|
15
|
+
data = notification.examples.filter_map do |example|
|
16
|
+
next if config.skip_record_proc.call(example)
|
17
|
+
|
18
|
+
TestMetrics.new(example, time).data
|
19
|
+
end
|
20
|
+
return logger.warn("#{LOG_PREFIX} No test execution records found, metrics will not be exported!") if data.empty?
|
21
|
+
|
22
|
+
push_to_gcs(data)
|
23
|
+
push_to_clickhouse(data)
|
22
24
|
end
|
23
25
|
|
24
26
|
private
|
25
27
|
|
26
|
-
|
27
|
-
|
28
|
+
# Configuration instance
|
29
|
+
#
|
30
|
+
# @return [Config]
|
28
31
|
def config
|
29
32
|
Config.configuration
|
30
33
|
end
|
31
34
|
|
32
|
-
#
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
35
|
+
# Logger instance
|
36
|
+
#
|
37
|
+
# @return [Logger]
|
38
|
+
def logger
|
39
|
+
config.logger
|
40
|
+
end
|
41
|
+
|
42
|
+
# Single common timestamp for all exported example metrics to keep data points consistently grouped
|
43
|
+
#
|
44
|
+
# @return [Time]
|
45
|
+
def time
|
46
|
+
return @time if @time
|
47
|
+
|
48
|
+
ci_created_at = Runtime::Env.ci_pipeline_created_at
|
49
|
+
@time = ci_created_at ? Time.strptime(ci_created_at, '%Y-%m-%dT%H:%M:%S%z') : Time.now.utc
|
50
|
+
end
|
51
|
+
|
52
|
+
# Push data to gcs
|
53
|
+
#
|
54
|
+
# @param data [Array]
|
55
|
+
# @return [void]
|
56
|
+
def push_to_gcs(data)
|
57
|
+
return logger.debug("#{LOG_PREFIX} GCS configuration missing, skipping gcs export!") unless config.gcs_config
|
58
|
+
|
59
|
+
gcs_config = config.gcs_config
|
60
|
+
GcsTools.gcs_client(
|
61
|
+
project_id: gcs_config.project_id,
|
62
|
+
credentials: gcs_config.credentials,
|
63
|
+
dry_run: gcs_config.dry_run
|
64
|
+
).put_object(gcs_config.bucket_name, gcs_config.metrics_file_name, data.to_json)
|
65
|
+
logger.info("#{LOG_PREFIX} Successfully pushed #{data.size} entries to GCS bucket!")
|
66
|
+
rescue StandardError => e
|
67
|
+
logger.error("#{LOG_PREFIX} Error occurred while pushing metrics to GCS: #{e.message}")
|
68
|
+
end
|
69
|
+
|
70
|
+
# Push data to clickhouse
|
71
|
+
#
|
72
|
+
# @param data [Array<Hash>]
|
73
|
+
# @return [void]
|
74
|
+
def push_to_clickhouse(data)
|
75
|
+
return logger.debug("ClickHouse configuration missing, skipping ClickHouse export!") unless config.clickhouse_config
|
76
|
+
|
77
|
+
clickhouse_config = config.clickhouse_config
|
78
|
+
ClickHouse::Client.new(
|
79
|
+
url: clickhouse_config.url,
|
80
|
+
database: clickhouse_config.database,
|
81
|
+
username: clickhouse_config.username,
|
82
|
+
password: clickhouse_config.password,
|
83
|
+
logger: logger
|
84
|
+
).insert_json_data(clickhouse_config.table_name, data)
|
85
|
+
logger.info("#{LOG_PREFIX} Successfully pushed #{data.size} entries to ClickHouse!")
|
86
|
+
rescue StandardError => e
|
87
|
+
logger.error("#{LOG_PREFIX} Error occurred while pushing metrics to ClickHouse: #{e.message}")
|
51
88
|
end
|
52
|
-
# rubocop:enable Metrics/AbcSize
|
53
89
|
end
|
54
90
|
end
|
55
91
|
end
|
56
92
|
end
|
93
|
+
# rubocop:enable Metrics/AbcSize
|