gitlab_quality-test_tooling 2.16.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/exe/relate-failure-issue +5 -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/gitlab_client/issues_dry_client.rb +2 -2
- data/lib/gitlab_quality/test_tooling/report/failed_test_issue.rb +1 -1
- data/lib/gitlab_quality/test_tooling/report/flaky_test_issue.rb +2 -2
- data/lib/gitlab_quality/test_tooling/report/generate_test_session.rb +2 -2
- data/lib/gitlab_quality/test_tooling/report/group_issues/error_message_normalizer.rb +49 -0
- data/lib/gitlab_quality/test_tooling/report/group_issues/error_pattern_matcher.rb +36 -0
- data/lib/gitlab_quality/test_tooling/report/group_issues/failure_processor.rb +73 -0
- data/lib/gitlab_quality/test_tooling/report/group_issues/group_results_in_issues.rb +48 -0
- data/lib/gitlab_quality/test_tooling/report/group_issues/incident_checker.rb +61 -0
- data/lib/gitlab_quality/test_tooling/report/group_issues/issue_base.rb +48 -0
- data/lib/gitlab_quality/test_tooling/report/group_issues/issue_creator.rb +44 -0
- data/lib/gitlab_quality/test_tooling/report/group_issues/issue_finder.rb +79 -0
- data/lib/gitlab_quality/test_tooling/report/group_issues/issue_formatter.rb +83 -0
- data/lib/gitlab_quality/test_tooling/report/group_issues/issue_manager.rb +33 -0
- data/lib/gitlab_quality/test_tooling/report/group_issues/issue_updater.rb +87 -0
- data/lib/gitlab_quality/test_tooling/report/health_problem_reporter.rb +3 -3
- data/lib/gitlab_quality/test_tooling/report/knapsack_report_issue.rb +1 -1
- data/lib/gitlab_quality/test_tooling/report/merge_request_slow_tests_report.rb +2 -6
- data/lib/gitlab_quality/test_tooling/report/relate_failure_issue.rb +131 -4
- data/lib/gitlab_quality/test_tooling/report/slow_test_issue.rb +2 -1
- data/lib/gitlab_quality/test_tooling/runtime/env.rb +9 -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 +39 -11
- 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/test_result/base_test_result.rb +4 -0
- data/lib/gitlab_quality/test_tooling/version.rb +1 -1
- data/lib/gitlab_quality/test_tooling.rb +2 -0
- metadata +69 -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
|
@@ -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
|
|
@@ -5,131 +5,138 @@ require 'time'
|
|
|
5
5
|
module GitlabQuality
|
|
6
6
|
module TestTooling
|
|
7
7
|
module TestMetricsExporter
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def time
|
|
13
|
-
return @time if defined?(@time)
|
|
14
|
-
|
|
15
|
-
created_at = Time.strptime(env('CI_PIPELINE_CREATED_AT'), '%Y-%m-%dT%H:%M:%S%z') if env('CI_PIPELINE_CREATED_AT')
|
|
16
|
-
@time = Time.parse((created_at || Time.now).utc.strftime('%Y-%m-%d %H:%M:%S %z'))
|
|
8
|
+
class TestMetrics
|
|
9
|
+
def initialize(example, timestamp)
|
|
10
|
+
@example = example
|
|
11
|
+
@timestamp = timestamp
|
|
17
12
|
end
|
|
18
13
|
|
|
19
|
-
#
|
|
20
|
-
# Metrics tags
|
|
14
|
+
# Test data hash
|
|
21
15
|
#
|
|
22
|
-
# @param [RSpec::Core::Example] example
|
|
23
|
-
# @param [Array<String>] custom_keys
|
|
24
|
-
# @param [String]
|
|
25
16
|
# @return [Hash]
|
|
26
|
-
def
|
|
17
|
+
def data
|
|
27
18
|
{
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
job_name: job_name,
|
|
33
|
-
merge_request: merge_request,
|
|
34
|
-
run_type: run_type,
|
|
35
|
-
feature_category: example.metadata[:feature_category],
|
|
36
|
-
product_group: example.metadata[:product_group],
|
|
37
|
-
exception_class: example.execution_result.exception&.class&.to_s,
|
|
38
|
-
**custom_metrics(example.metadata, custom_keys)
|
|
19
|
+
time: timestamp,
|
|
20
|
+
**rspec_metrics,
|
|
21
|
+
**ci_metrics,
|
|
22
|
+
**custom_metrics
|
|
39
23
|
}.compact
|
|
40
24
|
end
|
|
41
|
-
# rubocop:enable Metrics/AbcSize
|
|
42
25
|
|
|
43
|
-
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
attr_reader :example, :timestamp
|
|
29
|
+
|
|
30
|
+
# Exporter configuration
|
|
31
|
+
#
|
|
32
|
+
# @return [Config]
|
|
33
|
+
def config
|
|
34
|
+
Config.configuration
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Rspec related metrics
|
|
44
38
|
#
|
|
45
|
-
# @param [RSpec::Core::Example] example
|
|
46
|
-
# @param [Array<String>] custom_keys
|
|
47
39
|
# @return [Hash]
|
|
48
|
-
def
|
|
40
|
+
def rspec_metrics # rubocop:disable Metrics/AbcSize
|
|
49
41
|
{
|
|
50
42
|
id: example.id,
|
|
43
|
+
name: example.full_description,
|
|
44
|
+
file_path: example.metadata[:file_path].sub(/\A./, ''),
|
|
45
|
+
status: example.execution_result.status,
|
|
51
46
|
run_time: (example.execution_result.run_time * 1000).round,
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
**custom_metrics(example.metadata, custom_keys)
|
|
59
|
-
}.compact
|
|
47
|
+
location: example_location,
|
|
48
|
+
exception_class: exception_class,
|
|
49
|
+
failure_exception: failure_exception,
|
|
50
|
+
quarantined: quarantined?,
|
|
51
|
+
test_retried: config.test_retried_proc.call(example)
|
|
52
|
+
}
|
|
60
53
|
end
|
|
61
54
|
|
|
62
|
-
#
|
|
63
|
-
#
|
|
64
|
-
# - if test is failed or pending, return rspec status
|
|
65
|
-
# - if test passed but had more than 1 attempt, consider test flaky
|
|
55
|
+
# CI related metrics
|
|
66
56
|
#
|
|
67
|
-
# @
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
57
|
+
# @return [Hash]
|
|
58
|
+
def ci_metrics
|
|
59
|
+
{
|
|
60
|
+
ci_project_id: env("CI_PROJECT_ID")&.to_i,
|
|
61
|
+
ci_project_path: env("CI_PROJECT_PATH"),
|
|
62
|
+
ci_job_name: ci_job_name,
|
|
63
|
+
ci_job_id: env('CI_JOB_ID')&.to_i,
|
|
64
|
+
ci_pipeline_id: env('CI_PIPELINE_ID')&.to_i,
|
|
65
|
+
ci_merge_request_iid: (env('CI_MERGE_REQUEST_IID') || env('TOP_UPSTREAM_MERGE_REQUEST_IID'))&.to_i,
|
|
66
|
+
ci_branch: env("CI_COMMIT_REF_NAME"),
|
|
67
|
+
ci_target_branch: env("CI_MERGE_REQUEST_TARGET_BRANCH_NAME")
|
|
68
|
+
}
|
|
74
69
|
end
|
|
75
70
|
|
|
76
|
-
#
|
|
71
|
+
# Additional custom metrics
|
|
77
72
|
#
|
|
78
|
-
# @
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
73
|
+
# @return [Hash]
|
|
74
|
+
def custom_metrics
|
|
75
|
+
metrics = example.metadata
|
|
76
|
+
.slice(*config.extra_rspec_metadata_keys)
|
|
77
|
+
.merge(config.custom_metrics_proc.call(example))
|
|
78
|
+
|
|
79
|
+
metrics.each_with_object({}) do |(k, value), custom_metrics|
|
|
80
|
+
custom_metrics[k.to_sym] = metrics_value(value)
|
|
81
|
+
end
|
|
82
82
|
end
|
|
83
83
|
|
|
84
84
|
# Checks if spec is quarantined
|
|
85
85
|
#
|
|
86
|
-
# @param [RSpec::Core::Example] example
|
|
87
86
|
# @return [String]
|
|
88
|
-
def quarantined
|
|
89
|
-
return
|
|
87
|
+
def quarantined?
|
|
88
|
+
return false unless example.metadata.key?(:quarantine)
|
|
90
89
|
|
|
91
90
|
# if quarantine key is present and status is pending, consider it quarantined
|
|
92
|
-
|
|
91
|
+
example.execution_result.status == :pending
|
|
93
92
|
end
|
|
94
93
|
|
|
95
94
|
# Base ci job name
|
|
96
95
|
#
|
|
97
96
|
# @return [String]
|
|
98
|
-
def
|
|
99
|
-
|
|
97
|
+
def ci_job_name
|
|
98
|
+
env("CI_JOB_NAME")&.gsub(%r{ \d{1,2}/\d{1,2}}, '')
|
|
100
99
|
end
|
|
101
100
|
|
|
102
|
-
#
|
|
101
|
+
# Example location
|
|
103
102
|
#
|
|
104
103
|
# @return [String]
|
|
105
|
-
def
|
|
106
|
-
|
|
104
|
+
def example_location
|
|
105
|
+
# ensures that location will be correct even in case of shared examples
|
|
106
|
+
file = example
|
|
107
|
+
.metadata
|
|
108
|
+
.fetch(:shared_group_inclusion_backtrace)
|
|
109
|
+
.last
|
|
110
|
+
&.formatted_inclusion_location
|
|
111
|
+
|
|
112
|
+
return example.location unless file
|
|
113
|
+
|
|
114
|
+
file
|
|
107
115
|
end
|
|
108
116
|
|
|
109
|
-
#
|
|
117
|
+
# Failure exception class
|
|
110
118
|
#
|
|
111
119
|
# @return [String]
|
|
112
|
-
def
|
|
113
|
-
|
|
120
|
+
def exception_class
|
|
121
|
+
example.execution_result.exception&.class&.to_s
|
|
114
122
|
end
|
|
115
123
|
|
|
116
|
-
#
|
|
124
|
+
# Truncated exception stacktrace
|
|
117
125
|
#
|
|
118
|
-
# @
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
return {} if custom_keys.nil?
|
|
123
|
-
|
|
124
|
-
custom_metrics = {}
|
|
125
|
-
custom_keys.each do |k|
|
|
126
|
-
value = metadata[k.to_sym]
|
|
127
|
-
v = value.is_a?(Numeric) || value.nil? ? value : value.to_s
|
|
126
|
+
# @return [String]
|
|
127
|
+
def failure_exception
|
|
128
|
+
example.execution_result.exception.then do |exception|
|
|
129
|
+
next unless exception
|
|
128
130
|
|
|
129
|
-
|
|
131
|
+
exception.to_s.tr("\n", " ").slice(0, 1000)
|
|
130
132
|
end
|
|
133
|
+
end
|
|
131
134
|
|
|
132
|
-
|
|
135
|
+
# Test run type | suite name
|
|
136
|
+
#
|
|
137
|
+
# @return [String]
|
|
138
|
+
def run_type
|
|
139
|
+
config.run_type || ci_job_name || "unknown"
|
|
133
140
|
end
|
|
134
141
|
|
|
135
142
|
# Return non empty environment variable value
|
|
@@ -141,6 +148,24 @@ module GitlabQuality
|
|
|
141
148
|
|
|
142
149
|
ENV.fetch(name)
|
|
143
150
|
end
|
|
151
|
+
|
|
152
|
+
# Metrics value cast to a valid type
|
|
153
|
+
#
|
|
154
|
+
# @param value [Object]
|
|
155
|
+
# @return [Object]
|
|
156
|
+
def metrics_value(value)
|
|
157
|
+
return value if value.is_a?(Numeric) || value.is_a?(String) || bool?(value) || value.nil?
|
|
158
|
+
|
|
159
|
+
value.to_s
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Value is a true or false
|
|
163
|
+
#
|
|
164
|
+
# @param val [Object]
|
|
165
|
+
# @return [Boolean]
|
|
166
|
+
def bool?(val)
|
|
167
|
+
[true, false].include?(val)
|
|
168
|
+
end
|
|
144
169
|
end
|
|
145
170
|
end
|
|
146
171
|
end
|
|
@@ -7,6 +7,8 @@ module GitlabQuality
|
|
|
7
7
|
module TestTooling
|
|
8
8
|
Error = Class.new(StandardError)
|
|
9
9
|
loader = Zeitwerk::Loader.new
|
|
10
|
+
loader.push_dir(__dir__.to_s, namespace: GitlabQuality)
|
|
11
|
+
loader.ignore("#{__dir__}/test_tooling.rb")
|
|
10
12
|
loader.push_dir("#{__dir__}/test_tooling", namespace: GitlabQuality::TestTooling)
|
|
11
13
|
loader.ignore("#{__dir__}/test_tooling/version.rb")
|
|
12
14
|
|