ci_runner 0.4.0 → 0.5.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: 6573071a75fdc013ce2cb451567a5610d4928d8e10e336bba6296b1b046f1b82
4
- data.tar.gz: b171257681c40b76b71c5113c243bdc9ee3992e2522b3128e980527eaab151bf
3
+ metadata.gz: 361a2617b4dfcf8a20ed8566286fed6b10c88f9618d712dfd939520e31ab3400
4
+ data.tar.gz: 39a23722fd556bac27f95fd5417bf0a6b3db5d736c6effbc4ea55e9025f91965
5
5
  SHA512:
6
- metadata.gz: 40a798ad29846ca92722b56ef4f1bae2a1f76358de371ebf95fec1053b135882cbbb494430d77cc720ed3d60795413b994fc7288261e1c4d2bc981b0a003b45f
7
- data.tar.gz: c1cde1a84e5c4cdc87b4763a85388373b187a43518f72330a3a1c7855f51ca33f358b1831977d708105731ebd61c75c25184f7cfb4ec9abcb7c0b32458e247d1
6
+ metadata.gz: 4e904af08558ae212fe8cf2ca471d56c74ff25a74c416c085478bd6f3e840cde12fd5e4f2e9a5444d00c6d92ee1ce1caf5b19d432a9294947a6451c6c19907bb
7
+ data.tar.gz: a1c780586d26f82d1524450cb930c4be253017f1d739b6be6504fe76907ec09283bd8ba6b542cca92850f9d33f437c4ed58b1c8fa2002c544b2f38c9995c44dc
data/ci_runner.gemspec CHANGED
@@ -34,10 +34,12 @@ Gem::Specification.new do |spec|
34
34
  spec.executables = ["ci_runner"]
35
35
  spec.require_paths = ["lib"]
36
36
 
37
- spec.add_dependency("cli-ui", "~> 1.5")
37
+ spec.add_dependency("cli-ui", "~> 2.7")
38
+ spec.add_dependency("drb")
38
39
  spec.add_dependency("rake", "~> 13.0")
39
40
  spec.add_dependency("thor", "~> 1.2")
40
41
 
42
+ spec.add_development_dependency("base64")
41
43
  spec.add_development_dependency("rspec", "~> 3.11")
42
44
  spec.add_development_dependency("rubocop-shopify", "~> 2.8")
43
45
  spec.add_development_dependency("webmock", "~> 3.14")
@@ -39,14 +39,18 @@ module CIRunner
39
39
  def download_log
40
40
  uri = URI(url)
41
41
  _, org, pipeline, _, build = uri.path.split("/")
42
- @client = Client::Buildkite.new
42
+ @token = nil
43
+ client = Client::Buildkite.new
43
44
 
44
- unless @client.public_build?(org, pipeline, build)
45
- token = retrieve_token_from_config(org, url)
46
- @client = Client::AuthenticatedBuildkite.new(token)
45
+ unless client.public_build?(org, pipeline, build)
46
+ @token = retrieve_token_from_config(org, url)
47
+ client = Client::AuthenticatedBuildkite.new(@token)
47
48
  end
48
49
 
49
- @client.job_logs(org, pipeline, build).each do |log_url|
50
+ # Remember the client class so each concurrent download can build its own instance in #process.
51
+ @client_class = client.class
52
+
53
+ client.job_logs(org, pipeline, build).each do |log_url|
50
54
  @queue << log_url
51
55
  end
52
56
 
@@ -55,14 +59,17 @@ module CIRunner
55
59
 
56
60
  private
57
61
 
62
+ # Net::HTTP is not thread-safe, so each concurrent download gets its own client instance
63
+ # rather than sharing (and repeatedly resetting) a single one across threads.
64
+ #
58
65
  # @param url [String]
59
66
  #
60
67
  # @return [void]
61
68
  def process(url)
62
- @client.reset!
63
- response = @client.download_log(url)
69
+ response = @client_class.new(@token).download_log(url)
70
+ content = response.read
64
71
 
65
- @tempfile.write(response.read)
72
+ @write_mutex.synchronize { @tempfile.write(content) }
66
73
  end
67
74
 
68
75
  # Retrieve a Buildkite token from the user confg.
@@ -113,7 +113,7 @@ module CIRunner
113
113
  parsed_response = JSON.parse(response.read)
114
114
  log_output = parsed_response.map! { |res| res["message"] }.join
115
115
 
116
- @tempfile.write(log_output)
116
+ @write_mutex.synchronize { @tempfile.write(log_output) }
117
117
  end
118
118
 
119
119
  # The URL on the commit status will look something like: https://circleci.com/gh/owner/repo/1234?query_string.
@@ -13,6 +13,7 @@ module CIRunner
13
13
  def initialize(...)
14
14
  @queue = Queue.new
15
15
  @tempfile = Tempfile.new
16
+ @write_mutex = Mutex.new
16
17
 
17
18
  super(...)
18
19
  end
data/lib/ci_runner/cli.rb CHANGED
@@ -29,9 +29,9 @@ module CIRunner
29
29
  Please note that CI Runner will **not** ensure that the Git HEAD of your local repository matches
30
30
  the commit that failed upstream.
31
31
  EOM
32
- option :commit, type: :string, desc: "The Git commit that was pushed to GitHub and has a failing CI. The HEAD commit of your local repository will be used by default." # rubocop:disable Layout/LineLength
33
- option :repository, type: :string, desc: "The repository on which the CI failed. The repository will be infered from your git remote by default.", banner: "catanacorp/catana" # rubocop:disable Layout/LineLength
34
- option :run_name, type: :string, desc: "The CI check you which to rerun in case multiple checks failed for a commit. CI Runner will prompt you by default." # rubocop:disable Layout/LineLength
32
+ option :commit, type: :string, desc: "The Git commit that was pushed to GitHub and has a failing CI. The HEAD commit of your local repository will be used by default."
33
+ option :repository, type: :string, desc: "The repository on which the CI failed. The repository will be infered from your git remote by default.", banner: "catanacorp/catana"
34
+ option :run_name, type: :string, desc: "The CI check you which to rerun in case multiple checks failed for a commit. CI Runner will prompt you by default."
35
35
  def rerun
36
36
  ::CLI::UI::StdoutRouter.enable
37
37
 
@@ -45,16 +45,20 @@ module CIRunner
45
45
  get("/repos/#{repository}/commits/#{commit}/check-runs")
46
46
  end
47
47
 
48
- # Makes an API request to get the Commit statuses for the +commit+.
48
+ # Makes an API request to get the combined Commit status for the +commit+.
49
+ #
50
+ # Unlike the "list commit statuses" endpoint (which is append-only and returns every status
51
+ # update), the combined status endpoint returns only the latest status per context, matching
52
+ # what GitHub displays. This avoids presenting duplicate checks to the user.
49
53
  #
50
54
  # @param repository [String] The full repository name, including the owner (rails/rails)
51
55
  # @param commit [String] The Git commit that has been pushed to GitHub.
52
56
  #
53
57
  # @return [Hash] See GitHub documentation.
54
58
  #
55
- # @see https://docs.github.com/en/rest/commits/statuses#list-commit-statuses-for-a-reference
56
- def commit_statuses(repository, commit)
57
- get("/repos/#{repository}/commits/#{commit}/statuses")
59
+ # @see https://docs.github.com/en/rest/commits/statuses#get-the-combined-status-for-a-specific-reference
60
+ def commit_status(repository, commit)
61
+ get("/repos/#{repository}/commits/#{commit}/status")
58
62
  end
59
63
 
60
64
  # Makes two requests to get the CI log for a check run.
@@ -183,6 +183,44 @@ module CIRunner
183
183
  value.nil? ? true : value
184
184
  end
185
185
 
186
+ # Whether CI Runner should detect the Ruby version used on CI and re-run the tests with the
187
+ # matching Ruby from ~/.rubies.
188
+ #
189
+ # Set this to +false+ for projects whose gems are bound to a specific interpreter/gem path
190
+ # that CI Runner shouldn't override (e.g. a Nix- or shadowenv-managed monorepo). When false,
191
+ # the rerun uses the Ruby and gems of the environment CI Runner is invoked from.
192
+ #
193
+ # @return [Boolean] True by default.
194
+ #
195
+ # @example Storing this configuration
196
+ # `cat myproject/.github/ci_runner.yml`
197
+ #
198
+ # ---
199
+ # detect_ruby: false
200
+ def detect_ruby?
201
+ value = @yaml_config.dig("detect_ruby")
202
+
203
+ value.nil? ? true : value
204
+ end
205
+
206
+ # Whether CI Runner should detect the Gemfile used on CI and re-run the tests with it.
207
+ #
208
+ # Set this to +false+ for the same reasons as +detect_ruby?+: when the environment CI Runner
209
+ # runs in already provides the correct set of dependencies.
210
+ #
211
+ # @return [Boolean] True by default.
212
+ #
213
+ # @example Storing this configuration
214
+ # `cat myproject/.github/ci_runner.yml`
215
+ #
216
+ # ---
217
+ # detect_gemfile: false
218
+ def detect_gemfile?
219
+ value = @yaml_config.dig("detect_gemfile")
220
+
221
+ value.nil? ? true : value
222
+ end
223
+
186
224
  # Main detection regex used to detect test failures.
187
225
  #
188
226
  # **Important** This regexp 3 named capture groups. The order doesn't matter.
@@ -49,11 +49,11 @@ module CIRunner
49
49
  when seed_regex
50
50
  @seed = first_matching_group(Regexp.last_match)
51
51
  when ruby_detection_regex
52
- @ruby_version = first_matching_group(Regexp.last_match)
52
+ @ruby_version = first_matching_group(Regexp.last_match) if Configuration::Project.instance.detect_ruby?
53
53
 
54
54
  @buffer << line_no_ansi_color if buffering?
55
55
  when gemfile_detection_regex
56
- @gemfile = first_matching_group(Regexp.last_match)
56
+ @gemfile = first_matching_group(Regexp.last_match) if Configuration::Project.instance.detect_gemfile?
57
57
  when buffer_detection_regex
58
58
  if Configuration::Project.instance.process_on_new_match? && buffering?
59
59
  process_buffer
@@ -66,12 +66,14 @@ module CIRunner
66
66
 
67
67
  minitest_plugin_path = File.expand_path("../..", __dir__)
68
68
  rake_load_path = Gem.loaded_specs["rake"].full_require_paths.first
69
+ drb_load_path = Gem.loaded_specs["drb"].full_require_paths.first
69
70
 
70
71
  code = <<~EOM
71
72
  Rake::TestTask.new(:__ci_runner_test) do |t|
72
73
  t.libs << "test"
73
74
  t.libs << "lib"
74
75
  t.libs << "#{rake_load_path}"
76
+ t.libs << "#{drb_load_path}"
75
77
  t.libs << "#{minitest_plugin_path}"
76
78
  t.test_files = #{failures.map(&:path)}
77
79
  t.ruby_opts = ["-W0"] if ENV["NO_WARNING"]
@@ -122,7 +124,7 @@ module CIRunner
122
124
  #
123
125
  # For failure, Minitest will print the location of the test file, but it can be wrong.
124
126
  #
125
- # MaintenanceTasks::RunsTest#test_run_a_CSV_Task [/home/runner/work/maintenance_tasks/maintenance_tasks/vendor/bundle/ruby/2.7.0/gems/capybara-3.37.1/lib/capybara/minitest.rb:295] # rubocop:disable Layout/LineLength
127
+ # MaintenanceTasks::RunsTest#test_run_a_CSV_Task [/home/runner/work/maintenance_tasks/maintenance_tasks/vendor/bundle/ruby/2.7.0/gems/capybara-3.37.1/lib/capybara/minitest.rb:295]
126
128
  #
127
129
  # In this case the location of the file points to a file inside a gem, which is for sure not where the test lives.
128
130
  # When this happen, we discard the location provided by Minitest and try to match another possible location.
@@ -64,10 +64,10 @@ module CIRunner
64
64
  # @see https://docs.github.com/en/rest/checks/runs#get-a-check-run
65
65
  def other_ci(repository, commit)
66
66
  github_client = Client::Github.new(Configuration::User.instance.github_token)
67
- commit_statuses = github_client.commit_statuses(repository, commit)
67
+ commit_status = github_client.commit_status(repository, commit)
68
68
 
69
- commit_statuses.map do |commit_status|
70
- check_class_from_url(commit_status, repository, commit)
69
+ commit_status["statuses"].map do |status|
70
+ check_class_from_url(status, repository, commit)
71
71
  end.compact
72
72
  end
73
73
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CIRunner
4
- VERSION = "0.4.0"
4
+ VERSION = "0.5.0"
5
5
  end
@@ -25,6 +25,11 @@ module Minitest
25
25
  "#{failure.klass}##{failure.test_name}" == runnable
26
26
  end
27
27
  end
28
+
29
+ # Vanilla Minitest filters with `filter === runnable`, but the minitest-reporters gem's
30
+ # DelegateReporter calls `filter.match?(runnable)` instead. Alias the two so CI Runner
31
+ # works whether or not the project under test uses minitest-reporters.
32
+ alias_method :match?, :===
28
33
  end
29
34
 
30
35
  options[:filter] = filter.new(failures)
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ci_runner
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Edouard Chin
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2024-01-04 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: cli-ui
@@ -16,14 +15,28 @@ dependencies:
16
15
  requirements:
17
16
  - - "~>"
18
17
  - !ruby/object:Gem::Version
19
- version: '1.5'
18
+ version: '2.7'
20
19
  type: :runtime
21
20
  prerelease: false
22
21
  version_requirements: !ruby/object:Gem::Requirement
23
22
  requirements:
24
23
  - - "~>"
25
24
  - !ruby/object:Gem::Version
26
- version: '1.5'
25
+ version: '2.7'
26
+ - !ruby/object:Gem::Dependency
27
+ name: drb
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
27
40
  - !ruby/object:Gem::Dependency
28
41
  name: rake
29
42
  requirement: !ruby/object:Gem::Requirement
@@ -52,6 +65,20 @@ dependencies:
52
65
  - - "~>"
53
66
  - !ruby/object:Gem::Version
54
67
  version: '1.2'
68
+ - !ruby/object:Gem::Dependency
69
+ name: base64
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
55
82
  - !ruby/object:Gem::Dependency
56
83
  name: rspec
57
84
  requirement: !ruby/object:Gem::Requirement
@@ -146,7 +173,6 @@ metadata:
146
173
  changelog_uri: https://github.com/Edouard-chin/ci_runner/blob/main/CHANGELOG.md
147
174
  allowed_push_host: https://rubygems.org
148
175
  rubygems_mfa_required: 'true'
149
- post_install_message:
150
176
  rdoc_options: []
151
177
  require_paths:
152
178
  - lib
@@ -161,8 +187,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
161
187
  - !ruby/object:Gem::Version
162
188
  version: '0'
163
189
  requirements: []
164
- rubygems_version: 3.5.3
165
- signing_key:
190
+ rubygems_version: 4.0.16
166
191
  specification_version: 4
167
192
  summary: Re-run failing tests from CI on your local machine without copy/pasting.
168
193
  test_files: []