rspec-mergify 0.0.0.dev
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 +7 -0
- data/LICENSE +674 -0
- data/README.md +66 -0
- data/lib/mergify/rspec/ci_insights.rb +174 -0
- data/lib/mergify/rspec/configuration.rb +101 -0
- data/lib/mergify/rspec/flaky_detection.rb +252 -0
- data/lib/mergify/rspec/formatter.rb +221 -0
- data/lib/mergify/rspec/quarantine.rb +90 -0
- data/lib/mergify/rspec/resources/ci.rb +24 -0
- data/lib/mergify/rspec/resources/git.rb +35 -0
- data/lib/mergify/rspec/resources/github_actions.rb +60 -0
- data/lib/mergify/rspec/resources/jenkins.rb +55 -0
- data/lib/mergify/rspec/resources/mergify.rb +24 -0
- data/lib/mergify/rspec/resources/rspec.rb +22 -0
- data/lib/mergify/rspec/synchronous_batch_span_processor.rb +34 -0
- data/lib/mergify/rspec/utils.rb +140 -0
- data/lib/mergify/rspec/version.rb +18 -0
- data/lib/mergify/rspec.rb +11 -0
- data/lib/rspec_mergify.rb +12 -0
- metadata +104 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rspec/core/formatters/base_formatter'
|
|
4
|
+
require 'opentelemetry-sdk'
|
|
5
|
+
|
|
6
|
+
module Mergify
|
|
7
|
+
module RSpec
|
|
8
|
+
# RSpec formatter that creates OpenTelemetry spans for CI Insights and
|
|
9
|
+
# prints a terminal report. It is purely observational and does not modify
|
|
10
|
+
# test execution.
|
|
11
|
+
# rubocop:disable Metrics/ClassLength
|
|
12
|
+
class Formatter < ::RSpec::Core::Formatters::BaseFormatter
|
|
13
|
+
::RSpec::Core::Formatters.register self,
|
|
14
|
+
:start,
|
|
15
|
+
:example_started,
|
|
16
|
+
:example_finished,
|
|
17
|
+
:example_pending,
|
|
18
|
+
:stop
|
|
19
|
+
|
|
20
|
+
# rubocop:disable Metrics/MethodLength
|
|
21
|
+
def start(notification)
|
|
22
|
+
super
|
|
23
|
+
|
|
24
|
+
@ci_insights = Mergify::RSpec.ci_insights
|
|
25
|
+
return unless @ci_insights&.tracer
|
|
26
|
+
|
|
27
|
+
extract_distributed_trace_context
|
|
28
|
+
|
|
29
|
+
@session_span = @ci_insights.tracer.start_span(
|
|
30
|
+
'rspec session start',
|
|
31
|
+
with_parent: @parent_context,
|
|
32
|
+
attributes: { 'test.scope' => 'session' }
|
|
33
|
+
)
|
|
34
|
+
@has_error = false
|
|
35
|
+
@example_spans = {}
|
|
36
|
+
end
|
|
37
|
+
# rubocop:enable Metrics/MethodLength
|
|
38
|
+
|
|
39
|
+
def example_started(notification)
|
|
40
|
+
return unless @ci_insights&.tracer && @session_span
|
|
41
|
+
|
|
42
|
+
example = notification.example
|
|
43
|
+
parent_context = OpenTelemetry::Trace.context_with_span(@session_span)
|
|
44
|
+
quarantined = @ci_insights.mark_test_as_quarantined_if_needed(example.id)
|
|
45
|
+
|
|
46
|
+
span = @ci_insights.tracer.start_span(
|
|
47
|
+
example.id,
|
|
48
|
+
with_parent: parent_context,
|
|
49
|
+
attributes: build_example_attributes(example, quarantined)
|
|
50
|
+
)
|
|
51
|
+
@example_spans[example.id] = span
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# rubocop:disable Metrics/MethodLength
|
|
55
|
+
def example_finished(notification)
|
|
56
|
+
return unless @example_spans
|
|
57
|
+
|
|
58
|
+
example = notification.example
|
|
59
|
+
span = @example_spans.delete(example.id)
|
|
60
|
+
return unless span
|
|
61
|
+
|
|
62
|
+
result = example.execution_result
|
|
63
|
+
status = result.status.to_s
|
|
64
|
+
span.set_attribute('test.case.result.status', status)
|
|
65
|
+
set_flaky_attributes(span, example)
|
|
66
|
+
|
|
67
|
+
if result.status == :failed
|
|
68
|
+
set_error_attributes(span, result.exception)
|
|
69
|
+
@has_error = true
|
|
70
|
+
else
|
|
71
|
+
span.status = OpenTelemetry::Trace::Status.ok
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
span.finish
|
|
75
|
+
end
|
|
76
|
+
# rubocop:enable Metrics/MethodLength
|
|
77
|
+
|
|
78
|
+
def example_pending(notification)
|
|
79
|
+
return unless @example_spans
|
|
80
|
+
|
|
81
|
+
example = notification.example
|
|
82
|
+
span = @example_spans.delete(example.id)
|
|
83
|
+
return unless span
|
|
84
|
+
|
|
85
|
+
span.set_attribute('test.case.result.status', 'skipped')
|
|
86
|
+
span.finish
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def stop(_notification)
|
|
90
|
+
finish_session_span
|
|
91
|
+
print_report
|
|
92
|
+
flush_and_shutdown
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
private
|
|
96
|
+
|
|
97
|
+
def extract_distributed_trace_context
|
|
98
|
+
traceparent = ENV.fetch('MERGIFY_TRACEPARENT', nil)
|
|
99
|
+
@parent_context = if traceparent
|
|
100
|
+
propagator = OpenTelemetry::Trace::Propagation::TraceContext::TextMapPropagator.new
|
|
101
|
+
propagator.extract({ 'traceparent' => traceparent })
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def build_example_attributes(example, quarantined)
|
|
106
|
+
{
|
|
107
|
+
'test.scope' => 'case',
|
|
108
|
+
'code.filepath' => example.metadata[:file_path].delete_prefix('./'),
|
|
109
|
+
'code.function' => example.description,
|
|
110
|
+
'code.lineno' => example.metadata[:line_number] || 0,
|
|
111
|
+
'code.namespace' => example.example_group.description,
|
|
112
|
+
'code.file.path' => File.expand_path(example.metadata[:file_path]),
|
|
113
|
+
'code.line.number' => example.metadata[:line_number] || 0,
|
|
114
|
+
'cicd.test.quarantined' => quarantined
|
|
115
|
+
}
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def set_flaky_attributes(span, example)
|
|
119
|
+
meta = example.metadata
|
|
120
|
+
|
|
121
|
+
rerun_count = meta[:mergify_rerun_count]
|
|
122
|
+
span.set_attribute('cicd.test.rerun_count', rerun_count) unless rerun_count.nil?
|
|
123
|
+
|
|
124
|
+
flaky = meta[:mergify_flaky]
|
|
125
|
+
span.set_attribute('cicd.test.flaky', flaky) unless flaky.nil?
|
|
126
|
+
|
|
127
|
+
flaky_detection = meta[:mergify_flaky_detection]
|
|
128
|
+
span.set_attribute('cicd.test.flaky_detection', flaky_detection) unless flaky_detection.nil?
|
|
129
|
+
|
|
130
|
+
new_test = meta[:mergify_new_test]
|
|
131
|
+
span.set_attribute('cicd.test.new', new_test) unless new_test.nil?
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def set_error_attributes(span, exception)
|
|
135
|
+
span.set_attribute('exception.type', exception.class.to_s)
|
|
136
|
+
span.set_attribute('exception.message', exception.message)
|
|
137
|
+
span.status = OpenTelemetry::Trace::Status.error(exception.message)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def finish_session_span
|
|
141
|
+
return unless @session_span
|
|
142
|
+
|
|
143
|
+
@session_span.status = if @has_error
|
|
144
|
+
OpenTelemetry::Trace::Status.error('One or more tests failed')
|
|
145
|
+
else
|
|
146
|
+
OpenTelemetry::Trace::Status.ok
|
|
147
|
+
end
|
|
148
|
+
@session_span.finish
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# rubocop:disable Metrics/MethodLength
|
|
152
|
+
def print_report
|
|
153
|
+
output.puts ''
|
|
154
|
+
output.puts '--- Mergify CI ---'
|
|
155
|
+
|
|
156
|
+
unless @ci_insights
|
|
157
|
+
output.puts 'Mergify CI Insights is not configured.'
|
|
158
|
+
return
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
print_configuration_warnings
|
|
162
|
+
print_flaky_report
|
|
163
|
+
print_quarantine_report
|
|
164
|
+
output.puts "MERGIFY_TEST_RUN_ID=#{@ci_insights.test_run_id}"
|
|
165
|
+
output.puts '------------------'
|
|
166
|
+
end
|
|
167
|
+
# rubocop:enable Metrics/MethodLength
|
|
168
|
+
|
|
169
|
+
def print_configuration_warnings
|
|
170
|
+
output.puts 'WARNING: MERGIFY_TOKEN is not set. Traces will not be sent to Mergify.' unless @ci_insights.token
|
|
171
|
+
|
|
172
|
+
return if @ci_insights.repo_name
|
|
173
|
+
|
|
174
|
+
output.puts 'WARNING: Could not detect repository name. ' \
|
|
175
|
+
'Please set GITHUB_REPOSITORY or configure a git remote.'
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def print_flaky_report
|
|
179
|
+
return unless @ci_insights.flaky_detector.respond_to?(:make_report)
|
|
180
|
+
|
|
181
|
+
report = @ci_insights.flaky_detector.make_report
|
|
182
|
+
output.puts report if report
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def print_quarantine_report
|
|
186
|
+
return unless @ci_insights.quarantined_tests.respond_to?(:report)
|
|
187
|
+
|
|
188
|
+
report = @ci_insights.quarantined_tests.report
|
|
189
|
+
output.puts report if report
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def flush_and_shutdown # rubocop:disable Metrics/MethodLength
|
|
193
|
+
return unless @ci_insights&.tracer_provider
|
|
194
|
+
|
|
195
|
+
begin
|
|
196
|
+
@ci_insights.tracer_provider.force_flush
|
|
197
|
+
rescue StandardError => e
|
|
198
|
+
print_export_error(e)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
begin
|
|
202
|
+
@ci_insights.tracer_provider.shutdown
|
|
203
|
+
rescue StandardError => e
|
|
204
|
+
output.puts "Error while shutting down the tracer: #{e.message}"
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def print_export_error(error)
|
|
209
|
+
output.puts "Error while exporting traces: #{error.message}"
|
|
210
|
+
output.puts ''
|
|
211
|
+
output.puts 'Common issues:'
|
|
212
|
+
output.puts ' - Your MERGIFY_TOKEN might not be set or could be invalid'
|
|
213
|
+
output.puts ' - CI Insights might not be enabled for this repository'
|
|
214
|
+
output.puts ' - There might be a network connectivity issue with the Mergify API'
|
|
215
|
+
output.puts ''
|
|
216
|
+
output.puts 'Documentation: https://docs.mergify.com/ci-insights/test-frameworks/'
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
# rubocop:enable Metrics/ClassLength
|
|
220
|
+
end
|
|
221
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'net/http'
|
|
4
|
+
require 'json'
|
|
5
|
+
require 'uri'
|
|
6
|
+
require 'set'
|
|
7
|
+
require_relative 'utils'
|
|
8
|
+
|
|
9
|
+
module Mergify
|
|
10
|
+
module RSpec
|
|
11
|
+
# Fetches quarantined test names from the Mergify API and tracks which are used.
|
|
12
|
+
class Quarantine
|
|
13
|
+
attr_reader :quarantined_tests, :init_error_msg
|
|
14
|
+
|
|
15
|
+
def initialize(api_url:, token:, repo_name:, branch_name:)
|
|
16
|
+
@repo_name = repo_name
|
|
17
|
+
@branch_name = branch_name
|
|
18
|
+
@quarantined_tests = []
|
|
19
|
+
@used_tests = Set.new
|
|
20
|
+
@init_error_msg = nil
|
|
21
|
+
|
|
22
|
+
owner, repo = Utils.split_full_repo_name(repo_name)
|
|
23
|
+
fetch_quarantined_tests(api_url, token, owner, repo, branch_name)
|
|
24
|
+
rescue Utils::InvalidRepositoryFullNameError => e
|
|
25
|
+
@init_error_msg = e.message
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def include?(example_id)
|
|
29
|
+
@quarantined_tests.include?(example_id)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def mark_as_used(example_id)
|
|
33
|
+
@used_tests.add(example_id)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# rubocop:disable Metrics/MethodLength,Metrics/AbcSize
|
|
37
|
+
def report
|
|
38
|
+
used, unused = @quarantined_tests.partition { |t| @used_tests.include?(t) }
|
|
39
|
+
|
|
40
|
+
lines = []
|
|
41
|
+
lines << 'Mergify Quarantine Report'
|
|
42
|
+
lines << " Repository : #{@repo_name}"
|
|
43
|
+
lines << " Branch : #{@branch_name}"
|
|
44
|
+
lines << " Quarantined tests from API: #{@quarantined_tests.size}"
|
|
45
|
+
lines << ''
|
|
46
|
+
lines << " Quarantined tests run (#{used.size}):"
|
|
47
|
+
used.each { |t| lines << " - #{t}" }
|
|
48
|
+
lines << ''
|
|
49
|
+
lines << " Unused quarantined tests (#{unused.size}):"
|
|
50
|
+
unused.each { |t| lines << " - #{t}" }
|
|
51
|
+
lines.join("\n")
|
|
52
|
+
end
|
|
53
|
+
# rubocop:enable Metrics/MethodLength,Metrics/AbcSize
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
# rubocop:disable Metrics/MethodLength,Metrics/AbcSize
|
|
58
|
+
def fetch_quarantined_tests(api_url, token, owner, repo, branch_name)
|
|
59
|
+
uri = URI("#{api_url}/v1/ci/#{owner}/repositories/#{repo}/quarantines")
|
|
60
|
+
uri.query = URI.encode_www_form(branch: branch_name)
|
|
61
|
+
|
|
62
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
63
|
+
http.use_ssl = uri.scheme == 'https'
|
|
64
|
+
http.open_timeout = 10
|
|
65
|
+
http.read_timeout = 10
|
|
66
|
+
|
|
67
|
+
request = Net::HTTP::Get.new(uri)
|
|
68
|
+
request['Authorization'] = "Bearer #{token}"
|
|
69
|
+
|
|
70
|
+
response = http.request(request)
|
|
71
|
+
handle_response(response)
|
|
72
|
+
rescue Net::OpenTimeout, Net::ReadTimeout, Errno::ECONNREFUSED, SocketError => e
|
|
73
|
+
@init_error_msg = "Failed to connect to Mergify API: #{e.message}"
|
|
74
|
+
end
|
|
75
|
+
# rubocop:enable Metrics/MethodLength,Metrics/AbcSize
|
|
76
|
+
|
|
77
|
+
def handle_response(response)
|
|
78
|
+
case response.code.to_i
|
|
79
|
+
when 200
|
|
80
|
+
data = JSON.parse(response.body)
|
|
81
|
+
@quarantined_tests = data.fetch('quarantined_tests', []).map { |t| t['test_name'] }
|
|
82
|
+
when 402
|
|
83
|
+
# No subscription — silently skip
|
|
84
|
+
else
|
|
85
|
+
@init_error_msg = "Mergify API returned HTTP #{response.code}"
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'opentelemetry-sdk'
|
|
4
|
+
require_relative '../utils'
|
|
5
|
+
|
|
6
|
+
module Mergify
|
|
7
|
+
module RSpec
|
|
8
|
+
module Resources
|
|
9
|
+
# Detects OpenTelemetry Resource attributes for the CI provider.
|
|
10
|
+
module CI
|
|
11
|
+
module_function
|
|
12
|
+
|
|
13
|
+
def detect
|
|
14
|
+
provider = Utils.ci_provider
|
|
15
|
+
return OpenTelemetry::SDK::Resources::Resource.create({}) if provider.nil?
|
|
16
|
+
|
|
17
|
+
OpenTelemetry::SDK::Resources::Resource.create(
|
|
18
|
+
'cicd.provider.name' => provider.to_s
|
|
19
|
+
)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'opentelemetry-sdk'
|
|
4
|
+
require_relative '../utils'
|
|
5
|
+
|
|
6
|
+
module Mergify
|
|
7
|
+
module RSpec
|
|
8
|
+
module Resources
|
|
9
|
+
# Detects OpenTelemetry Resource attributes from git.
|
|
10
|
+
module Git
|
|
11
|
+
module_function
|
|
12
|
+
|
|
13
|
+
GIT_MAPPING = {
|
|
14
|
+
'vcs.ref.head.name' => [:to_s, -> { Utils.git('rev-parse', '--abbrev-ref', 'HEAD') }],
|
|
15
|
+
'vcs.ref.head.revision' => [:to_s, -> { Utils.git('rev-parse', 'HEAD') }],
|
|
16
|
+
'vcs.repository.url.full' => [:to_s, -> { Utils.git('config', '--get', 'remote.origin.url') }],
|
|
17
|
+
'vcs.repository.name' => [
|
|
18
|
+
:to_s,
|
|
19
|
+
lambda {
|
|
20
|
+
url = Utils.git('config', '--get', 'remote.origin.url')
|
|
21
|
+
Utils.repository_name_from_url(url) if url
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
}.freeze
|
|
25
|
+
|
|
26
|
+
def detect
|
|
27
|
+
return OpenTelemetry::SDK::Resources::Resource.create({}) if Utils.ci_provider.nil?
|
|
28
|
+
|
|
29
|
+
attributes = Utils.get_attributes(GIT_MAPPING)
|
|
30
|
+
OpenTelemetry::SDK::Resources::Resource.create(attributes)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'opentelemetry-sdk'
|
|
5
|
+
require_relative '../utils'
|
|
6
|
+
|
|
7
|
+
module Mergify
|
|
8
|
+
module RSpec
|
|
9
|
+
module Resources
|
|
10
|
+
# Detects OpenTelemetry Resource attributes for GitHub Actions.
|
|
11
|
+
module GitHubActions
|
|
12
|
+
module_function
|
|
13
|
+
|
|
14
|
+
def detect
|
|
15
|
+
return OpenTelemetry::SDK::Resources::Resource.create({}) if Utils.ci_provider != :github_actions
|
|
16
|
+
|
|
17
|
+
attributes = Utils.get_attributes(GHA_MAPPING)
|
|
18
|
+
OpenTelemetry::SDK::Resources::Resource.create(attributes)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
GHA_MAPPING = {
|
|
22
|
+
'cicd.pipeline.name' => [:to_s, 'GITHUB_WORKFLOW'],
|
|
23
|
+
'cicd.pipeline.task.name' => [:to_s, 'GITHUB_JOB'],
|
|
24
|
+
'cicd.pipeline.run.id' => [:to_i, 'GITHUB_RUN_ID'],
|
|
25
|
+
'cicd.pipeline.run.attempt' => [:to_i, 'GITHUB_RUN_ATTEMPT'],
|
|
26
|
+
'cicd.pipeline.runner.name' => [:to_s, 'RUNNER_NAME'],
|
|
27
|
+
'vcs.ref.head.name' => [:to_s, -> { head_ref_name }],
|
|
28
|
+
'vcs.ref.head.type' => [:to_s, 'GITHUB_REF_TYPE'],
|
|
29
|
+
'vcs.ref.base.name' => [:to_s, 'GITHUB_BASE_REF'],
|
|
30
|
+
'vcs.repository.name' => [:to_s, 'GITHUB_REPOSITORY'],
|
|
31
|
+
'vcs.repository.id' => [:to_i, 'GITHUB_REPOSITORY_ID'],
|
|
32
|
+
'vcs.repository.url.full' => [:to_s, -> { repository_url }],
|
|
33
|
+
'vcs.ref.head.revision' => [:to_s, -> { head_sha }]
|
|
34
|
+
}.freeze
|
|
35
|
+
|
|
36
|
+
def head_ref_name
|
|
37
|
+
ref = ENV.fetch('GITHUB_HEAD_REF', '')
|
|
38
|
+
ref.empty? ? ENV.fetch('GITHUB_REF_NAME', nil) : ref
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def repository_url
|
|
42
|
+
server = ENV.fetch('GITHUB_SERVER_URL', nil)
|
|
43
|
+
repo = ENV.fetch('GITHUB_REPOSITORY', nil)
|
|
44
|
+
"#{server}/#{repo}" if server && repo
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def head_sha
|
|
48
|
+
if ENV.fetch('GITHUB_EVENT_NAME', nil) == 'pull_request'
|
|
49
|
+
event_path = ENV.fetch('GITHUB_EVENT_PATH', nil)
|
|
50
|
+
if event_path && File.file?(event_path)
|
|
51
|
+
event = JSON.parse(File.read(event_path))
|
|
52
|
+
return event.dig('pull_request', 'head', 'sha').to_s
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
ENV.fetch('GITHUB_SHA', nil)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'opentelemetry-sdk'
|
|
4
|
+
require_relative '../utils'
|
|
5
|
+
require_relative 'git'
|
|
6
|
+
|
|
7
|
+
module Mergify
|
|
8
|
+
module RSpec
|
|
9
|
+
module Resources
|
|
10
|
+
# Detects OpenTelemetry Resource attributes for Jenkins.
|
|
11
|
+
module Jenkins
|
|
12
|
+
module_function
|
|
13
|
+
|
|
14
|
+
GIT_BRANCH_PREFIXES = %w[origin/ refs/heads/].freeze
|
|
15
|
+
|
|
16
|
+
JENKINS_MAPPING = {
|
|
17
|
+
'cicd.pipeline.name' => [:to_s, 'JOB_NAME'],
|
|
18
|
+
'cicd.pipeline.task.name' => [:to_s, 'JOB_NAME'],
|
|
19
|
+
'cicd.pipeline.run.id' => [:to_s, 'BUILD_ID'],
|
|
20
|
+
'cicd.pipeline.run.url' => [:to_s, 'BUILD_URL'],
|
|
21
|
+
'cicd.pipeline.runner.name' => [:to_s, 'NODE_NAME'],
|
|
22
|
+
'vcs.ref.head.name' => [:to_s, -> { branch }],
|
|
23
|
+
'vcs.ref.head.revision' => [:to_s, 'GIT_COMMIT'],
|
|
24
|
+
'vcs.repository.url.full' => [:to_s, 'GIT_URL'],
|
|
25
|
+
'vcs.repository.name' => [
|
|
26
|
+
:to_s,
|
|
27
|
+
lambda {
|
|
28
|
+
url = ENV.fetch('GIT_URL', nil)
|
|
29
|
+
Utils.repository_name_from_url(url) if url
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
}.freeze
|
|
33
|
+
|
|
34
|
+
def detect
|
|
35
|
+
return OpenTelemetry::SDK::Resources::Resource.create({}) if Utils.ci_provider != :jenkins
|
|
36
|
+
|
|
37
|
+
git_attrs = Utils.get_attributes(Git::GIT_MAPPING)
|
|
38
|
+
jenkins_attrs = Utils.get_attributes(JENKINS_MAPPING)
|
|
39
|
+
merged = git_attrs.merge(jenkins_attrs)
|
|
40
|
+
OpenTelemetry::SDK::Resources::Resource.create(merged)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def branch
|
|
44
|
+
raw = ENV.fetch('GIT_BRANCH', nil)
|
|
45
|
+
return nil unless raw
|
|
46
|
+
|
|
47
|
+
GIT_BRANCH_PREFIXES.each do |prefix|
|
|
48
|
+
return raw[prefix.length..] if raw.start_with?(prefix)
|
|
49
|
+
end
|
|
50
|
+
raw
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'opentelemetry-sdk'
|
|
4
|
+
require_relative '../utils'
|
|
5
|
+
|
|
6
|
+
module Mergify
|
|
7
|
+
module RSpec
|
|
8
|
+
module Resources
|
|
9
|
+
# Detects OpenTelemetry Resource attributes for Mergify-specific fields.
|
|
10
|
+
module Mergify
|
|
11
|
+
module_function
|
|
12
|
+
|
|
13
|
+
MERGIFY_MAPPING = {
|
|
14
|
+
'mergify.test.job.name' => [:to_s, 'MERGIFY_TEST_JOB_NAME']
|
|
15
|
+
}.freeze
|
|
16
|
+
|
|
17
|
+
def detect
|
|
18
|
+
attributes = Utils.get_attributes(MERGIFY_MAPPING)
|
|
19
|
+
OpenTelemetry::SDK::Resources::Resource.create(attributes)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'opentelemetry-sdk'
|
|
4
|
+
require 'rspec/core/version'
|
|
5
|
+
|
|
6
|
+
module Mergify
|
|
7
|
+
module RSpec
|
|
8
|
+
module Resources
|
|
9
|
+
# Detects OpenTelemetry Resource attributes for RSpec.
|
|
10
|
+
module RSpec
|
|
11
|
+
module_function
|
|
12
|
+
|
|
13
|
+
def detect
|
|
14
|
+
OpenTelemetry::SDK::Resources::Resource.create(
|
|
15
|
+
'test.framework' => 'rspec',
|
|
16
|
+
'test.framework.version' => ::RSpec::Core::Version::STRING
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'opentelemetry-sdk'
|
|
4
|
+
|
|
5
|
+
module Mergify
|
|
6
|
+
module RSpec
|
|
7
|
+
class ExportError < StandardError; end
|
|
8
|
+
|
|
9
|
+
# A span processor that queues spans in memory and exports them all in one
|
|
10
|
+
# batch when force_flush is called. This avoids HTTP requests during test
|
|
11
|
+
# execution.
|
|
12
|
+
class SynchronousBatchSpanProcessor < OpenTelemetry::SDK::Trace::Export::SimpleSpanProcessor
|
|
13
|
+
def initialize(exporter)
|
|
14
|
+
super
|
|
15
|
+
@queue = []
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def on_finish(span)
|
|
19
|
+
return unless span.context.trace_flags.sampled?
|
|
20
|
+
|
|
21
|
+
@queue << span.to_span_data
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def force_flush(timeout: nil) # rubocop:disable Lint/UnusedMethodArgument
|
|
25
|
+
spans = @queue.dup
|
|
26
|
+
@queue.clear
|
|
27
|
+
result = @span_exporter.export(spans)
|
|
28
|
+
raise ExportError, 'Failed to export traces' unless result == OpenTelemetry::SDK::Trace::Export::SUCCESS
|
|
29
|
+
|
|
30
|
+
OpenTelemetry::SDK::Trace::Export::SUCCESS
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|