rspec-mergify 0.1.1 → 0.1.3

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: a69562ff8faf56e93122d4a11c50cf94e4d328a06a57b84cbc7eff1a80dc3dc9
4
- data.tar.gz: d5cdd6a938db90e73a0771a2646b0aa9f303c120ffd72e26df8c02df3a19cbbe
3
+ metadata.gz: 968b0ced5510f59941eb189080af9e540dc8cd2799757bb16f0dbe746204471e
4
+ data.tar.gz: 307dff0317e1abf576472b0428a1e3f0f8d7f1b1b9257c84ecfd81f0fac9152c
5
5
  SHA512:
6
- metadata.gz: 4d6f8fdc77e89ecb4a69c3c44804b2b75ee6749cd4f0f664e903629a6216e4c1be7860a0a774df2b1c90e6eee233f18a5d1e9d07b94949b5c106972f7849cb30
7
- data.tar.gz: 2228d6143a76bfba2f0e955ee6ca988d89786e0aeedb9ed2a00d2dde40678dff529f4dd276a30fc7b31f77290389da1b09e40f467021203f578fa760b84c5ee8
6
+ metadata.gz: 23673868ac2acd11145346748cea2e5c1cae71cb6c55371ad1440a2f9bdf3436ddf0ebaaef9ae2bfd7a56e5d435b67c3f449b3161201becbd3a7b864dea8d2ad
7
+ data.tar.gz: 2d676dd9fc25b6383db1013e42513d5f70208a6796f4fe4fa5544493d123736871b6d7617c9590c7f8fc8fd2fa4ffb99cf1b683c9cf6df0d124c6a8a27822c05
@@ -8,6 +8,7 @@ require_relative 'resources/ci'
8
8
  require_relative 'resources/git'
9
9
  require_relative 'resources/github_actions'
10
10
  require_relative 'resources/jenkins'
11
+ require_relative 'resources/buildkite'
11
12
  require_relative 'resources/mergify'
12
13
  require_relative 'resources/rspec'
13
14
 
@@ -102,6 +103,7 @@ module Mergify
102
103
  Resources::Git.detect,
103
104
  Resources::GitHubActions.detect,
104
105
  Resources::Jenkins.detect,
106
+ Resources::Buildkite.detect,
105
107
  Resources::Mergify.detect,
106
108
  Resources::RSpec.detect
107
109
  ]
@@ -143,7 +145,6 @@ module Mergify
143
145
  # rubocop:disable Metrics/MethodLength
144
146
  def load_flaky_detector
145
147
  return unless @token && @repo_name
146
- return unless Utils.env_truthy?('_MERGIFY_TEST_NEW_FLAKY_DETECTION')
147
148
 
148
149
  require_relative 'flaky_detection'
149
150
  mode = @base_branch_name ? 'new' : 'unhealthy'
@@ -153,6 +154,10 @@ module Mergify
153
154
  full_repository_name: @repo_name,
154
155
  mode: mode
155
156
  )
157
+ rescue FlakyDetectionDisabledError
158
+ # The repository has not opted into flaky detection, or has no baseline
159
+ # yet. Both are expected; skip without surfacing an error.
160
+ nil
156
161
  rescue StandardError => e
157
162
  @flaky_detector_error_message = "Could not load flaky detector: #{e.message}"
158
163
  end
@@ -8,6 +8,12 @@ require_relative 'utils'
8
8
 
9
9
  module Mergify
10
10
  module RSpec
11
+ # Signals that flaky detection must not run for this session, and that this
12
+ # is expected rather than a failure: the repository has not opted in (the
13
+ # server responds with 404) or there is no baseline of recorded tests yet.
14
+ # Callers skip silently instead of surfacing an error banner.
15
+ class FlakyDetectionDisabledError < StandardError; end
16
+
11
17
  # Manages intelligent test rerunning with budget constraints for flaky detection.
12
18
  # rubocop:disable Metrics/ClassLength
13
19
  class FlakyDetector
@@ -193,7 +199,7 @@ module Mergify
193
199
 
194
200
  private
195
201
 
196
- # rubocop:disable Metrics/AbcSize
202
+ # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
197
203
  def fetch_context
198
204
  owner, repo = Utils.split_full_repo_name(@full_repository_name)
199
205
  uri = URI("#{@url}/v1/ci/#{owner}/repositories/#{repo}/flaky-detection-context")
@@ -207,9 +213,18 @@ module Mergify
207
213
  request['Authorization'] = "Bearer #{@token}"
208
214
 
209
215
  response = http.request(request)
210
- parse_context(response.body)
216
+ case response.code.to_i
217
+ when 200
218
+ parse_context(response.body)
219
+ when 404
220
+ # A 404 means the repository has not opted into flaky detection; this
221
+ # is the expected default, not an error.
222
+ raise FlakyDetectionDisabledError
223
+ else
224
+ raise "Mergify API returned HTTP #{response.code}"
225
+ end
211
226
  end
212
- # rubocop:enable Metrics/AbcSize
227
+ # rubocop:enable Metrics/AbcSize,Metrics/MethodLength
213
228
 
214
229
  # rubocop:disable Metrics/MethodLength,Metrics/AbcSize
215
230
  def parse_context(body)
@@ -231,7 +246,9 @@ module Mergify
231
246
  def validate!
232
247
  return unless @mode == 'new' && @context[:existing_test_names].empty?
233
248
 
234
- raise 'Cannot use "new" mode without existing test names in the context'
249
+ # Without a baseline, `new` mode would treat every test as new and rerun
250
+ # the whole suite. Skip instead of surfacing an error.
251
+ raise FlakyDetectionDisabledError
235
252
  end
236
253
 
237
254
  def remaining_budget
@@ -54,11 +54,52 @@ module Mergify
54
54
 
55
55
  private
56
56
 
57
- # rubocop:disable Metrics/MethodLength,Metrics/AbcSize
58
57
  def fetch_quarantined_tests(api_url, token, owner, repo, branch_name)
59
58
  uri = URI("#{api_url}/v1/ci/#{owner}/repositories/#{repo}/quarantines")
60
- uri.query = URI.encode_www_form(branch: branch_name)
59
+ uri.query = URI.encode_www_form(branch: branch_name, per_page: 100)
60
+ collected = walk_paginated_quarantines(uri, token)
61
+ @quarantined_tests = collected if collected
62
+ rescue Net::OpenTimeout, Net::ReadTimeout, Errno::ECONNREFUSED, SocketError => e
63
+ @init_error_msg = "Failed to connect to Mergify API: #{e.message}"
64
+ rescue JSON::ParserError => e
65
+ @init_error_msg = "Mergify API returned a malformed quarantine list: #{e.message}"
66
+ end
61
67
 
68
+ # Follows the RFC 5988 `next` link until exhausted. Returns the full list
69
+ # on success, or `nil` when the run was aborted (subscription missing or
70
+ # an error already recorded in @init_error_msg).
71
+ # rubocop:disable Metrics/MethodLength,Metrics/AbcSize
72
+ def walk_paginated_quarantines(uri, token)
73
+ collected = []
74
+ # Guard against a server returning a `next` link that loops back to a
75
+ # URL we have already fetched.
76
+ seen = Set.new
77
+ while uri
78
+ if seen.include?(uri.to_s)
79
+ @init_error_msg = 'Mergify API returned a cyclic `next` link, aborting.'
80
+ return nil
81
+ end
82
+ seen.add(uri.to_s)
83
+
84
+ response = perform_request(uri, token)
85
+ case response.code.to_i
86
+ when 200
87
+ data = JSON.parse(response.body)
88
+ collected.concat(data.fetch('quarantined_tests', []).map { |t| t['test_name'] })
89
+ next_url = parse_next_link(response['Link'])
90
+ uri = next_url ? URI(next_url) : nil
91
+ when 402
92
+ return nil
93
+ else
94
+ @init_error_msg = "Mergify API returned HTTP #{response.code}"
95
+ return nil
96
+ end
97
+ end
98
+ collected
99
+ end
100
+ # rubocop:enable Metrics/MethodLength,Metrics/AbcSize
101
+
102
+ def perform_request(uri, token)
62
103
  http = Net::HTTP.new(uri.host, uri.port)
63
104
  http.use_ssl = uri.scheme == 'https'
64
105
  http.open_timeout = 10
@@ -66,23 +107,27 @@ module Mergify
66
107
 
67
108
  request = Net::HTTP::Get.new(uri)
68
109
  request['Authorization'] = "Bearer #{token}"
110
+ http.request(request)
111
+ end
69
112
 
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}"
113
+ # Parses RFC 8288 Link headers tolerantly: accepts both quoted
114
+ # (`rel="next"`) and token (`rel=next`) forms, and matches when `next`
115
+ # is one of several space-separated rel-types (`rel="next prev"`).
116
+ def parse_next_link(link_header)
117
+ return nil if link_header.nil? || link_header.empty?
118
+
119
+ link_header.split(',').each do |part|
120
+ match = part.strip.match(/\A<([^>]+)>\s*;\s*(.+)\z/)
121
+ next unless match && next_rel?(match[2])
122
+
123
+ return match[1]
124
+ end
125
+ nil
74
126
  end
75
- # rubocop:enable Metrics/MethodLength,Metrics/AbcSize
76
127
 
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}"
128
+ def next_rel?(params)
129
+ params.scan(/rel\s*=\s*(?:"([^"]+)"|([^\s;,]+))/i).any? do |quoted, token|
130
+ (quoted || token).split.include?('next')
86
131
  end
87
132
  end
88
133
  end
@@ -0,0 +1,54 @@
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
@@ -15,6 +15,7 @@ module Mergify
15
15
  'GITHUB_ACTIONS' => :github_actions,
16
16
  'CIRCLECI' => :circleci,
17
17
  'JENKINS_URL' => :jenkins,
18
+ 'BUILDKITE' => :buildkite,
18
19
  '_RSPEC_MERGIFY_TEST' => :rspec_mergify_suite
19
20
  }.freeze
20
21
 
@@ -125,6 +126,9 @@ module Mergify
125
126
  when :circleci
126
127
  url = ENV.fetch('CIRCLE_REPOSITORY_URL', nil)
127
128
  return repository_name_from_url(url) if url
129
+ when :buildkite
130
+ url = ENV.fetch('BUILDKITE_REPO', nil)
131
+ return repository_name_from_url(url) if url
128
132
  when :rspec_mergify_suite
129
133
  return 'Mergifyio/rspec-mergify'
130
134
  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.1
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mergify
@@ -67,6 +67,7 @@ files:
67
67
  - lib/mergify/rspec/flaky_detection.rb
68
68
  - lib/mergify/rspec/formatter.rb
69
69
  - lib/mergify/rspec/quarantine.rb
70
+ - lib/mergify/rspec/resources/buildkite.rb
70
71
  - lib/mergify/rspec/resources/ci.rb
71
72
  - lib/mergify/rspec/resources/git.rb
72
73
  - lib/mergify/rspec/resources/github_actions.rb