rspec-mergify 0.1.4 → 0.2.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: e3b9e1b83fe9ad90add227b5618ac7b4594883cf39c0eb58ab150feb0de9d916
4
- data.tar.gz: e801b0a101413dea6c0d723e58d79c4a978cffdf252ca35332a64e47c4b75493
3
+ metadata.gz: 2f6831a7105994678c61d541f3ca3840a618f93ed59561cecd23063d91063d7e
4
+ data.tar.gz: e6b1528a39efa7de31ef4ded5d44b2479f78521666cf48acb26edc6135580067
5
5
  SHA512:
6
- metadata.gz: 1a30b290d0860751b9a8be05cc18615415c0f3dc8ae16fbdd9ea877b54688767625e4d931e88f901ecbf8ca2dddc2e02eee457958ca31e7fb8fb305088912b66
7
- data.tar.gz: 643aa383dbaa5324534aea467d8220642f3240182e16d57447d73e4dd44e4a0337d4de9514233b13498802057638d4574311bb046d57cd93898ddf1747361b00
6
+ metadata.gz: acc68c52030c7cf29760e293f99a46faae5c012745f0cd6d9b81c36cbb5c29505b7d16662a1785fd641f3f4ba8e424b592782308ef06356c3694e9bbfe09bddc
7
+ data.tar.gz: c95fc1c14925f61b63d1888e51e47f88af3ced37565a4964e6c30b913f0cbc6261a212562578f41faef937c1a0665fc0a150b302fd83e270bc991bcefb76b663
@@ -3,13 +3,8 @@
3
3
  require 'securerandom'
4
4
  require 'opentelemetry-sdk'
5
5
  require_relative 'utils'
6
+ require_relative 'native'
6
7
  require_relative 'synchronous_batch_span_processor'
7
- require_relative 'resources/ci'
8
- require_relative 'resources/git'
9
- require_relative 'resources/github_actions'
10
- require_relative 'resources/jenkins'
11
- require_relative 'resources/buildkite'
12
- require_relative 'resources/mergify'
13
8
  require_relative 'resources/rspec'
14
9
 
15
10
  module Mergify
@@ -26,7 +21,7 @@ module Mergify
26
21
  # rubocop:disable-next Metrics/MethodLength
27
22
  def initialize
28
23
  @token = ENV.fetch('MERGIFY_TOKEN', nil)
29
- @repo_name = Utils.repository_name
24
+ @repo_name = Native.detect_repository_name
30
25
  @api_url = ENV.fetch('MERGIFY_API_URL', 'https://api.mergify.com')
31
26
  @test_run_id = SecureRandom.hex(8)
32
27
  @tracer_provider = nil
@@ -95,22 +90,19 @@ module Mergify
95
90
  [processor, exp]
96
91
  end
97
92
 
98
- # rubocop:disable-next Metrics/MethodLength
93
+ # The cicd.* and vcs.* attributes come from the Rust core, which every
94
+ # Mergify test client shares, so a provider gains them everywhere at once.
95
+ # What stays here is what only Ruby knows: the test framework, and the id
96
+ # this run invented for itself.
99
97
  def build_resource
100
98
  resources = [
101
- Resources::CI.detect,
102
- Resources::Git.detect,
103
- Resources::GitHubActions.detect,
104
- Resources::Jenkins.detect,
105
- Resources::Buildkite.detect,
106
- Resources::Mergify.detect,
107
- Resources::RSpec.detect
99
+ OpenTelemetry::SDK::Resources::Resource.create(Native.detect_attributes),
100
+ Resources::RSpec.detect,
101
+ OpenTelemetry::SDK::Resources::Resource.create('test.run.id' => @test_run_id)
108
102
  ]
109
- base = resources.reduce(OpenTelemetry::SDK::Resources::Resource.create({})) do |merged, r|
103
+ resources.reduce(OpenTelemetry::SDK::Resources::Resource.create({})) do |merged, r|
110
104
  merged.merge(r)
111
105
  end
112
- run_id_resource = OpenTelemetry::SDK::Resources::Resource.create('test.run.id' => @test_run_id)
113
- base.merge(run_id_resource)
114
106
  end
115
107
 
116
108
  def extract_branch_name(resource)
@@ -1,10 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'net/http'
4
- require 'json'
5
- require 'uri'
6
3
  require 'set'
7
4
  require_relative 'utils'
5
+ require_relative 'native'
6
+ require_relative 'version'
8
7
 
9
8
  module Mergify
10
9
  module RSpec
@@ -74,34 +73,25 @@ module Mergify
74
73
  @tests_to_process = []
75
74
  @budget = 0.0
76
75
 
77
- fetch_context
78
- validate!
76
+ @context = fetch_context
77
+ raise FlakyDetectionDisabledError unless Native::Budget.should_run(@context, @mode)
79
78
  end
80
79
 
81
- # rubocop:disable-next Metrics/MethodLength,Metrics/AbcSize
80
+ # Which tests this session reruns, and how long it may spend doing it,
81
+ # both come from the shared budget engine -- so a Ruby suite and a Python
82
+ # one facing the same context spend the same time.
83
+ #
84
+ # The engine sizes the budget from the existing tests *in this session*,
85
+ # where this class counted every existing test the context knew about. A
86
+ # session running part of a suite was handed the whole suite's budget; it
87
+ # now gets its own.
82
88
  def prepare_for_session(test_ids)
83
- existing = Set.new(@context[:existing_test_names])
84
- unhealthy = Set.new(@context[:unhealthy_test_names])
85
-
86
- @tests_to_process =
87
- if @mode == 'new'
88
- test_ids.reject { |id| existing.include?(id) }
89
- else
90
- test_ids.select { |id| unhealthy.include?(id) }
91
- end
89
+ plan = Native::Budget.compute(@context, @mode, test_ids, [])
92
90
 
93
- budget_ratio = if @mode == 'new'
94
- @context[:budget_ratio_for_new_tests]
95
- else
96
- @context[:budget_ratio_for_unhealthy_tests]
97
- end
98
-
99
- mean_duration_s = @context[:existing_tests_mean_duration_ms] / 1000.0
100
- existing_count = @context[:existing_test_names].size
101
- min_budget_s = @context[:min_budget_duration_ms] / 1000.0
102
-
103
- ratio_budget = budget_ratio * mean_duration_s * existing_count
104
- @budget = [ratio_budget, min_budget_s].max
91
+ @tests_to_process = plan['tests_to_process']
92
+ # The engine works in milliseconds; everything downstream compares
93
+ # against RSpec's durations, which are seconds.
94
+ @budget = plan['available_budget_ms'] / 1000.0
105
95
  end
106
96
 
107
97
  # rubocop:disable-next Metrics/MethodLength
@@ -113,7 +103,7 @@ module Mergify
113
103
 
114
104
  return unless @tests_to_process.include?(test_id)
115
105
 
116
- if test_id.length > @context[:max_test_name_length]
106
+ if test_id.length > @context['max_test_name_length']
117
107
  @over_length_tests.add(test_id)
118
108
  return
119
109
  end
@@ -153,7 +143,7 @@ module Mergify
153
143
  return false unless @metrics.key?(test_id)
154
144
 
155
145
  metrics = @metrics[test_id]
156
- min_exec = @context[:min_test_execution_count]
146
+ min_exec = @context['min_test_execution_count']
157
147
  (metrics.initial_duration * min_exec) > metrics.remaining_time
158
148
  end
159
149
 
@@ -161,7 +151,7 @@ module Mergify
161
151
  return false unless @metrics.key?(test_id)
162
152
 
163
153
  metrics = @metrics[test_id]
164
- metrics.will_exceed_deadline? || metrics.rerun_count >= @context[:max_test_execution_count]
154
+ metrics.will_exceed_deadline? || metrics.rerun_count >= @context['max_test_execution_count']
165
155
  end
166
156
 
167
157
  def test_metrics(test_id)
@@ -196,54 +186,16 @@ module Mergify
196
186
 
197
187
  private
198
188
 
199
- # rubocop:disable-next Metrics/AbcSize,Metrics/MethodLength
189
+ # A nil context means the repository has not opted into flaky detection,
190
+ # which is the expected default rather than a failure.
200
191
  def fetch_context
201
- owner, repo = Utils.split_full_repo_name(@full_repository_name)
202
- uri = URI("#{@url}/v1/ci/#{owner}/repositories/#{repo}/flaky-detection-context")
203
-
204
- http = Net::HTTP.new(uri.host, uri.port)
205
- http.use_ssl = uri.scheme == 'https'
206
- http.open_timeout = 10
207
- http.read_timeout = 10
208
-
209
- request = Net::HTTP::Get.new(uri)
210
- request['Authorization'] = "Bearer #{@token}"
211
-
212
- response = http.request(request)
213
- case response.code.to_i
214
- when 200
215
- parse_context(response.body)
216
- when 404
217
- # A 404 means the repository has not opted into flaky detection; this
218
- # is the expected default, not an error.
219
- raise FlakyDetectionDisabledError
220
- else
221
- raise "Mergify API returned HTTP #{response.code}"
222
- end
223
- end
224
-
225
- # rubocop:disable-next Metrics/MethodLength,Metrics/AbcSize
226
- def parse_context(body)
227
- data = JSON.parse(body, symbolize_names: true)
228
- @context = {
229
- budget_ratio_for_new_tests: data[:budget_ratio_for_new_tests].to_f,
230
- budget_ratio_for_unhealthy_tests: data[:budget_ratio_for_unhealthy_tests].to_f,
231
- existing_test_names: Array(data[:existing_test_names]),
232
- existing_tests_mean_duration_ms: data[:existing_tests_mean_duration_ms].to_f,
233
- unhealthy_test_names: Array(data[:unhealthy_test_names]),
234
- max_test_execution_count: data[:max_test_execution_count].to_i,
235
- max_test_name_length: data[:max_test_name_length].to_i,
236
- min_budget_duration_ms: data[:min_budget_duration_ms].to_f,
237
- min_test_execution_count: data[:min_test_execution_count].to_i
238
- }
239
- end
192
+ raise FlakyDetectionDisabledError unless Native.available?
240
193
 
241
- def validate!
242
- return unless @mode == 'new' && @context[:existing_test_names].empty?
194
+ owner, repo = Utils.split_full_repo_name(@full_repository_name)
195
+ context = Native::Client.new(@url, @token, owner, repo, VERSION).fetch_flaky_context
196
+ raise FlakyDetectionDisabledError if context.nil?
243
197
 
244
- # Without a baseline, `new` mode would treat every test as new and rerun
245
- # the whole suite. Skip instead of surfacing an error.
246
- raise FlakyDetectionDisabledError
198
+ context
247
199
  end
248
200
 
249
201
  def remaining_budget
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mergify
4
+ module RSpec
5
+ # The Rust extension over mergify-ci-core: CI detection shared with the
6
+ # pytest and TypeScript clients.
7
+ #
8
+ # Loading is best-effort by design. A precompiled gem carries one extension
9
+ # per Ruby under `<ruby>/`, the source gem compiles one alongside this file,
10
+ # and a platform we publish neither for gets neither. Detection is one input
11
+ # to telemetry, not the point of the gem, so an absent extension degrades
12
+ # what we can report rather than breaking the suite under test -- the same
13
+ # fail-open posture the napi binding takes when a platform has no prebuilt
14
+ # binary. `load_error` keeps the reason, for callers that want to say so.
15
+ module Native
16
+ # Raised when a Mergify API call fails outright.
17
+ #
18
+ # Distinct from StandardError on purpose: callers degrade on an API
19
+ # failure, and a bare rescue there would swallow genuine bugs in the
20
+ # binding as though the backend were down.
21
+ class ApiError < StandardError; end
22
+
23
+ class << self
24
+ # The LoadError that prevented the extension loading, or nil.
25
+ attr_accessor :load_error
26
+
27
+ def available?
28
+ load_error.nil?
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ begin
36
+ # Precompiled gems ship lib/mergify/rspec/<ruby>/mergify_ci.<dlext>.
37
+ require_relative "#{RUBY_VERSION.to_f}/mergify_ci"
38
+ rescue LoadError
39
+ begin
40
+ # Source gem, and any local `rake compile`.
41
+ require_relative 'mergify_ci'
42
+ rescue LoadError => e
43
+ Mergify::RSpec::Native.load_error = e
44
+ end
45
+ end
46
+
47
+ unless Mergify::RSpec::Native.available?
48
+ # Stand in for the extension so callers can just ask, and get the same answer
49
+ # they would get from a machine that is not in CI. Branching on availability
50
+ # at every call site would only spread the same nil back through the caller.
51
+ module Mergify
52
+ module RSpec
53
+ module Native
54
+ class << self
55
+ def detect_provider
56
+ nil
57
+ end
58
+
59
+ def detect_repository_name
60
+ nil
61
+ end
62
+
63
+ def detect_attributes
64
+ {}
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -1,14 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'net/http'
4
- require 'json'
5
- require 'uri'
6
3
  require 'set'
7
4
  require_relative 'utils'
5
+ require_relative 'native'
6
+ require_relative 'version'
8
7
 
9
8
  module Mergify
10
9
  module RSpec
11
10
  # Fetches quarantined test names from the Mergify API and tracks which are used.
11
+ #
12
+ # The fetch itself -- pagination, the RFC 8288 `next` links, the status
13
+ # codes that mean "not subscribed" rather than "broken" -- belongs to the
14
+ # shared Rust client now, so every Mergify test client reads a quarantine
15
+ # list the same way. What stays here is what RSpec cares about: which of
16
+ # those tests this session actually ran, and the report at the end.
12
17
  class Quarantine
13
18
  attr_reader :quarantined_tests, :init_error_msg
14
19
 
@@ -19,10 +24,7 @@ module Mergify
19
24
  @used_tests = Set.new
20
25
  @init_error_msg = nil
21
26
 
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
27
+ fetch(api_url, token, branch_name)
26
28
  end
27
29
 
28
30
  def include?(example_id)
@@ -53,80 +55,21 @@ module Mergify
53
55
 
54
56
  private
55
57
 
56
- def fetch_quarantined_tests(api_url, token, owner, repo, branch_name)
57
- uri = URI("#{api_url}/v1/ci/#{owner}/repositories/#{repo}/quarantines")
58
- uri.query = URI.encode_www_form(branch: branch_name, per_page: 100)
59
- collected = walk_paginated_quarantines(uri, token)
60
- @quarantined_tests = collected if collected
61
- rescue Net::OpenTimeout, Net::ReadTimeout, Errno::ECONNREFUSED, SocketError => e
62
- @init_error_msg = "Failed to connect to Mergify API: #{e.message}"
63
- rescue JSON::ParserError => e
64
- @init_error_msg = "Mergify API returned a malformed quarantine list: #{e.message}"
65
- end
66
-
67
- # Follows the RFC 5988 `next` link until exhausted. Returns the full list
68
- # on success, or `nil` when the run was aborted (subscription missing or
69
- # an error already recorded in @init_error_msg).
70
- # rubocop:disable-next Metrics/MethodLength,Metrics/AbcSize
71
- def walk_paginated_quarantines(uri, token)
72
- collected = []
73
- # Guard against a server returning a `next` link that loops back to a
74
- # URL we have already fetched.
75
- seen = Set.new
76
- while uri
77
- if seen.include?(uri.to_s)
78
- @init_error_msg = 'Mergify API returned a cyclic `next` link, aborting.'
79
- return nil
80
- end
81
- seen.add(uri.to_s)
82
-
83
- response = perform_request(uri, token)
84
- case response.code.to_i
85
- when 200
86
- data = JSON.parse(response.body)
87
- collected.concat(data.fetch('quarantined_tests', []).map { |t| t['test_name'] })
88
- next_url = parse_next_link(response['Link'])
89
- uri = next_url ? URI(next_url) : nil
90
- when 402
91
- return nil
92
- else
93
- @init_error_msg = "Mergify API returned HTTP #{response.code}"
94
- return nil
95
- end
96
- end
97
- collected
98
- end
99
-
100
- def perform_request(uri, token)
101
- http = Net::HTTP.new(uri.host, uri.port)
102
- http.use_ssl = uri.scheme == 'https'
103
- http.open_timeout = 10
104
- http.read_timeout = 10
105
-
106
- request = Net::HTTP::Get.new(uri)
107
- request['Authorization'] = "Bearer #{token}"
108
- http.request(request)
109
- end
110
-
111
- # Parses RFC 8288 Link headers tolerantly: accepts both quoted
112
- # (`rel="next"`) and token (`rel=next`) forms, and matches when `next`
113
- # is one of several space-separated rel-types (`rel="next prev"`).
114
- def parse_next_link(link_header)
115
- return nil if link_header.nil? || link_header.empty?
116
-
117
- link_header.split(',').each do |part|
118
- match = part.strip.match(/\A<([^>]+)>\s*;\s*(.+)\z/)
119
- next unless match && next_rel?(match[2])
120
-
121
- return match[1]
58
+ # A nil list means the repository has no quarantine subscription, which is
59
+ # not an error: the session simply quarantines nothing. Anything that went
60
+ # genuinely wrong is recorded and the suite carries on -- this plugin has
61
+ # never let the backend fail a test run.
62
+ def fetch(api_url, token, branch_name)
63
+ unless Native.available?
64
+ @init_error_msg = "Mergify native extension unavailable: #{Native.load_error}"
65
+ return
122
66
  end
123
- nil
124
- end
125
67
 
126
- def next_rel?(params)
127
- params.scan(/rel\s*=\s*(?:"([^"]+)"|([^\s;,]+))/i).any? do |quoted, token|
128
- (quoted || token).split.include?('next')
129
- end
68
+ owner, repo = Utils.split_full_repo_name(@repo_name)
69
+ client = Native::Client.new(api_url, token, owner, repo, VERSION)
70
+ @quarantined_tests = client.fetch_quarantine(branch_name) || []
71
+ rescue Utils::InvalidRepositoryFullNameError, Native::ApiError => e
72
+ @init_error_msg = e.message
130
73
  end
131
74
  end
132
75
  end
@@ -1,24 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'open3'
4
-
5
3
  module Mergify
6
4
  module RSpec
7
5
  # Utility methods shared across the rspec-mergify gem.
6
+ #
7
+ # CI detection used to live here -- the provider table, the git shelling
8
+ # out, the per-provider environment mappings. It is the Rust core's now, via
9
+ # the extension in Mergify::RSpec::Native, so that every Mergify test client
10
+ # detects identically. What is left is what is genuinely Ruby's: parsing a
11
+ # repository name the API needs split, and deciding whether the plugin
12
+ # should switch itself on at all.
8
13
  module Utils
9
14
  module_function
10
15
 
11
16
  # Raised when a repository full name (owner/repo) is malformed.
12
17
  class InvalidRepositoryFullNameError < StandardError; end
13
18
 
14
- SUPPORTED_CIS = {
15
- 'GITHUB_ACTIONS' => :github_actions,
16
- 'CIRCLECI' => :circleci,
17
- 'JENKINS_URL' => :jenkins,
18
- 'BUILDKITE' => :buildkite,
19
- '_RSPEC_MERGIFY_TEST' => :rspec_mergify_suite
20
- }.freeze
21
-
22
19
  TRUTHY_STRINGS = %w[y yes t true on 1].freeze
23
20
  FALSY_STRINGS = %w[n no f false off 0].freeze
24
21
 
@@ -40,44 +37,14 @@ module Mergify
40
37
 
41
38
  # Returns true when the suite is running inside CI or when
42
39
  # RSPEC_MERGIFY_ENABLE is set to a truthy value.
40
+ #
41
+ # Deliberately not the core's provider detection: this asks whether the
42
+ # plugin should run, which an unrecognised CI or a developer setting
43
+ # RSPEC_MERGIFY_ENABLE both answer yes to.
43
44
  def in_ci?
44
45
  env_truthy?('CI') || env_truthy?('RSPEC_MERGIFY_ENABLE')
45
46
  end
46
47
 
47
- # Evaluates whether a CI environment variable should be considered enabled.
48
- # rubocop:disable-next Metrics/MethodLength
49
- def ci_provider
50
- SUPPORTED_CIS.each do |envvar, name|
51
- next unless ENV.key?(envvar)
52
-
53
- enabled =
54
- begin
55
- strtobool(ENV.fetch(envvar, ''))
56
- rescue ArgumentError
57
- !ENV.fetch(envvar, '').strip.empty?
58
- end
59
-
60
- return name if enabled
61
- end
62
- nil
63
- end
64
-
65
- # Parse a git remote URL (SSH or HTTPS) into "owner/repo" form.
66
- # Returns nil when the URL cannot be recognised.
67
- def repository_name_from_url(url)
68
- # SSH: git@github.com:owner/repo.git
69
- if (m = url.match(%r{\Agit@[\w.-]+:(?<full_name>[\w.-]+/[\w.-]+?)(?:\.git)?/?$}))
70
- return m[:full_name]
71
- end
72
-
73
- # HTTPS/HTTP with optional host (and optional port)
74
- if (m = url.match(%r{\A(?:https?://[\w.-]+(?::\d+)?/)?(?<full_name>[\w.-]+/[\w.-]+)/?\z}))
75
- return m[:full_name]
76
- end
77
-
78
- nil
79
- end
80
-
81
48
  # Split "owner/repo" into [owner, repo].
82
49
  # Raises InvalidRepositoryFullNameError when the format is wrong.
83
50
  def split_full_repo_name(full_repo_name)
@@ -86,55 +53,6 @@ module Mergify
86
53
 
87
54
  raise InvalidRepositoryFullNameError, "Invalid repository name: #{full_repo_name}"
88
55
  end
89
-
90
- # Run a git subcommand via Open3.
91
- # Returns stripped stdout on success, nil on failure.
92
- def git(*args)
93
- stdout, status = Open3.capture2('git', *args, err: File::NULL)
94
- status.success? ? stdout.strip : nil
95
- rescue StandardError
96
- nil
97
- end
98
-
99
- # Build an attribute hash from a mapping of
100
- # { attr_name => [cast_method_symbol, env_var_name_or_callable] }.
101
- # Attributes whose env var is unset or whose callable returns nil are omitted.
102
- def get_attributes(mapping)
103
- mapping.each_with_object({}) do |(attr, (cast, env_or_callable)), result|
104
- value = env_or_callable.respond_to?(:call) ? env_or_callable.call : ENV.fetch(env_or_callable, nil)
105
-
106
- next if value.nil?
107
- next if value.respond_to?(:empty?) && value.empty?
108
-
109
- result[attr] = value.public_send(cast)
110
- end
111
- end
112
-
113
- # Detect the repository name using CI environment variables or a git
114
- # remote fallback.
115
- # rubocop:disable-next Metrics/MethodLength,Metrics/CyclomaticComplexity
116
- def repository_name
117
- provider = ci_provider
118
-
119
- case provider
120
- when :jenkins
121
- url = ENV.fetch('GIT_URL', nil)
122
- return repository_name_from_url(url) if url
123
- when :github_actions
124
- return ENV.fetch('GITHUB_REPOSITORY', nil)
125
- when :circleci
126
- url = ENV.fetch('CIRCLE_REPOSITORY_URL', nil)
127
- return repository_name_from_url(url) if url
128
- when :buildkite
129
- url = ENV.fetch('BUILDKITE_REPO', nil)
130
- return repository_name_from_url(url) if url
131
- when :rspec_mergify_suite
132
- return 'Mergifyio/rspec-mergify'
133
- end
134
-
135
- url = git('config', '--get', 'remote.origin.url')
136
- repository_name_from_url(url) if url
137
- end
138
56
  end
139
57
  end
140
58
  end
@@ -8,6 +8,6 @@ module Mergify
8
8
  # which resolved against whatever repository happened to be the working
9
9
  # directory at load time -- the monorepo's namespaced tags here, the user's
10
10
  # own tags once the gem was installed.
11
- VERSION = '0.1.4'
11
+ VERSION = '0.2.0'
12
12
  end
13
13
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rspec-mergify
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mergify
@@ -66,13 +66,8 @@ files:
66
66
  - lib/mergify/rspec/configuration.rb
67
67
  - lib/mergify/rspec/flaky_detection.rb
68
68
  - lib/mergify/rspec/formatter.rb
69
+ - lib/mergify/rspec/native.rb
69
70
  - lib/mergify/rspec/quarantine.rb
70
- - lib/mergify/rspec/resources/buildkite.rb
71
- - lib/mergify/rspec/resources/ci.rb
72
- - lib/mergify/rspec/resources/git.rb
73
- - lib/mergify/rspec/resources/github_actions.rb
74
- - lib/mergify/rspec/resources/jenkins.rb
75
- - lib/mergify/rspec/resources/mergify.rb
76
71
  - lib/mergify/rspec/resources/rspec.rb
77
72
  - lib/mergify/rspec/synchronous_batch_span_processor.rb
78
73
  - lib/mergify/rspec/utils.rb
@@ -1,54 +0,0 @@
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 Buildkite.
11
- module Buildkite
12
- module_function
13
-
14
- BUILDKITE_MAPPING = {
15
- 'cicd.pipeline.name' => [:to_s, 'BUILDKITE_PIPELINE_SLUG'],
16
- 'cicd.pipeline.task.name' => [
17
- :to_s,
18
- lambda {
19
- label = ENV.fetch('BUILDKITE_LABEL', nil)
20
- label && !label.empty? ? label : ENV.fetch('BUILDKITE_STEP_KEY', nil)
21
- }
22
- ],
23
- 'cicd.pipeline.run.id' => [:to_s, 'BUILDKITE_BUILD_ID'],
24
- 'cicd.pipeline.run.url' => [:to_s, 'BUILDKITE_BUILD_URL'],
25
- 'cicd.pipeline.run.attempt' => [
26
- :to_i,
27
- -> { ENV.fetch('BUILDKITE_RETRY_COUNT', '0').to_i + 1 }
28
- ],
29
- 'cicd.pipeline.runner.name' => [:to_s, 'BUILDKITE_AGENT_NAME'],
30
- 'vcs.ref.head.name' => [:to_s, 'BUILDKITE_BRANCH'],
31
- 'vcs.ref.base.name' => [:to_s, 'BUILDKITE_PULL_REQUEST_BASE_BRANCH'],
32
- 'vcs.ref.head.revision' => [:to_s, 'BUILDKITE_COMMIT'],
33
- 'vcs.repository.url.full' => [:to_s, 'BUILDKITE_REPO'],
34
- 'vcs.repository.name' => [
35
- :to_s,
36
- lambda {
37
- url = ENV.fetch('BUILDKITE_REPO', nil)
38
- Utils.repository_name_from_url(url) if url
39
- }
40
- ]
41
- }.freeze
42
-
43
- def detect
44
- return OpenTelemetry::SDK::Resources::Resource.create({}) if Utils.ci_provider != :buildkite
45
-
46
- git_attrs = Utils.get_attributes(Git::GIT_MAPPING)
47
- buildkite_attrs = Utils.get_attributes(BUILDKITE_MAPPING)
48
- merged = git_attrs.merge(buildkite_attrs)
49
- OpenTelemetry::SDK::Resources::Resource.create(merged)
50
- end
51
- end
52
- end
53
- end
54
- end
@@ -1,24 +0,0 @@
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
@@ -1,35 +0,0 @@
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
@@ -1,60 +0,0 @@
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
@@ -1,55 +0,0 @@
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
@@ -1,24 +0,0 @@
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