Skip to content

Commit 6bb0829

Browse files
authored
Merge pull request #78 from ParentSquare/modernize
Release 0.12.0: modernize toolchain and CI matrix
2 parents 45dc417 + d6bbf0d commit 6bb0829

35 files changed

Lines changed: 228 additions & 174 deletions

.github/workflows/ci.yml

Lines changed: 33 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -12,30 +12,25 @@ jobs:
1212
strategy:
1313
fail-fast: false
1414
matrix:
15-
ruby: ['2.4', '2.5', '2.6', '2.7', '3.0', jruby-head, truffleruby-head]
16-
redis: ['4']
17-
search: [['opensearch-ruby:2.1.0', 'opensearchproject/opensearch:2.2.1']]
15+
ruby: ['3.1', '3.2', '3.3', '3.4', jruby-head, truffleruby-head]
16+
redis: ['5']
17+
search: [['opensearch-ruby:3.4.0', 'opensearchproject/opensearch:2.19.5']]
1818
include:
19-
# Redis 3
20-
- ruby: '2.7'
21-
redis: '3'
22-
search: ['opensearch-ruby:2.1.0', 'opensearchproject/opensearch:2.2.1']
23-
# Opensearch 1.0
24-
- ruby: '2.7'
19+
# Redis 4
20+
- ruby: '3.4'
2521
redis: '4'
26-
search: ['opensearch-ruby:1.0.1', 'opensearchproject/opensearch:1.0.1']
27-
# Elasticsearch 7.13
28-
- ruby: '2.7'
29-
redis: '4'
30-
search: ['elasticsearch:7.13.3', 'elasticsearch:7.13.4']
31-
# Redis 5
32-
- ruby: '2.7'
22+
search: ['opensearch-ruby:3.4.0', 'opensearchproject/opensearch:2.19.5']
23+
# OpenSearch 3.x
24+
- ruby: '3.4'
3325
redis: '5'
34-
search: ['opensearch-ruby:2.1.0', 'opensearchproject/opensearch:2.2.1']
35-
# Ruby 2.3 & Elasticsearch 7.5
36-
- ruby: '2.3'
37-
redis: '4'
38-
search: ['elasticsearch:7.5.0', 'elasticsearch:7.13.4']
26+
search: ['opensearch-ruby:3.4.0', 'opensearchproject/opensearch:3.0.0']
27+
# Elasticsearch 7.17 (last release of the pre-8 transport API
28+
# that the Faulty patch currently targets). ES 8+ moves the
29+
# transport into the `elastic-transport` gem under
30+
# `Elastic::Transport`, which the patch does not yet support.
31+
- ruby: '3.4'
32+
redis: '5'
33+
search: ['elasticsearch:7.17.11', 'elasticsearch:7.17.28']
3934
services:
4035
redis:
4136
image: redis
@@ -48,6 +43,13 @@ jobs:
4843
env:
4944
discovery.type: single-node
5045
plugins.security.disabled: ${{ contains(matrix.search[1], 'opensearch') && 'true' || '' }}
46+
xpack.security.enabled: ${{ contains(matrix.search[1], 'elasticsearch') && 'false' || '' }}
47+
# OpenSearch 2.12+ requires an initial admin password for the
48+
# security demo installer (which still runs even with
49+
# `plugins.security.disabled=true`). Skip the demo config
50+
# entirely so we don't have to set a placeholder password.
51+
# Older OpenSearch and all Elasticsearch images ignore this.
52+
DISABLE_INSTALL_DEMO_CONFIG: 'true'
5153
options: >-
5254
--health-cmd="curl http://localhost:9200/_cluster/health"
5355
--health-interval=3s
@@ -59,7 +61,7 @@ jobs:
5961
SEARCH_GEM: ${{ matrix.search[0] }}
6062

6163
steps:
62-
- uses: actions/checkout@v3
64+
- uses: actions/checkout@v4
6365
- uses: ruby/setup-ruby@v1
6466
with:
6567
ruby-version: ${{ matrix.ruby }}
@@ -70,39 +72,39 @@ jobs:
7072
env:
7173
MYSQL_USER: root
7274
MYSQL_PASSWORD: root
73-
- uses: codecov/codecov-action@v3
74-
if: matrix.ruby == '2.7'
75+
- uses: codecov/codecov-action@v5
76+
if: matrix.ruby == '3.4'
7577
with:
7678
files: coverage/coverage.xml
7779

7880
rubocop:
7981
runs-on: ubuntu-latest
8082
steps:
81-
- uses: actions/checkout@v3
83+
- uses: actions/checkout@v4
8284
- uses: ruby/setup-ruby@v1
8385
with:
84-
ruby-version: '2.7'
86+
ruby-version: '3.4'
8587
bundler-cache: true
8688
- run: bundle exec rubocop
8789

8890
yard:
8991
runs-on: ubuntu-latest
9092
steps:
91-
- uses: actions/checkout@v3
93+
- uses: actions/checkout@v4
9294
- uses: ruby/setup-ruby@v1
9395
with:
94-
ruby-version: '2.7'
96+
ruby-version: '3.4'
9597
bundler-cache: true
9698
- run: bin/yardoc --fail-on-warning
9799

98100
check_version:
99101
runs-on: ubuntu-latest
100102
if: startsWith(github.ref, 'refs/tags/v')
101103
steps:
102-
- uses: actions/checkout@v3
104+
- uses: actions/checkout@v4
103105
- uses: ruby/setup-ruby@v1
104106
with:
105-
ruby-version: '2.7'
107+
ruby-version: '3.4'
106108
bundler-cache: true
107109
- run: bin/check-version
108110

@@ -111,7 +113,7 @@ jobs:
111113
if: startsWith(github.ref, 'refs/tags/v')
112114
runs-on: ubuntu-latest
113115
steps:
114-
- uses: actions/checkout@v2
116+
- uses: actions/checkout@v4
115117
- uses: dawidd6/action-publish-gem@v1
116118
with:
117119
api_key: ${{secrets.RUBYGEMS_API_KEY}}

.rubocop.yml

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
---
2-
require:
2+
plugins:
33
- rubocop-rspec
44

55
AllCops:
6-
TargetRubyVersion: 2.3
6+
TargetRubyVersion: 3.1
7+
NewCops: enable
8+
SuggestExtensions: false
79

810
Gemspec/DeprecatedAttributeAssignment: { Enabled: true }
11+
# Faulty intentionally keeps essential dev deps in the gemspec; the rest
12+
# live in the Gemfile (per the comment in faulty.gemspec).
13+
Gemspec/DevelopmentDependencies: { Enabled: false }
914
Gemspec/RequireMFA: { Enabled: true }
1015

1116
Layout/ArgumentAlignment: { EnforcedStyle: with_fixed_indentation }
@@ -54,19 +59,22 @@ Lint/UselessRuby2Keywords: { Enabled: true }
5459

5560
RSpec/BeEq: { Enabled: true }
5661
RSpec/BeNil: { Enabled: true }
57-
RSpec/Capybara/SpecificMatcher: { Enabled: true }
5862
RSpec/ChangeByZero: { Enabled: true }
5963
RSpec/ExampleLength: { Enabled: false }
6064
RSpec/ExcessiveDocstringSpacing: { Enabled: true }
61-
RSpec/FactoryBot/SyntaxMethods: { Enabled: true }
62-
RSpec/FilePath: { Enabled: false }
6365
RSpec/IdenticalEqualityAssertion: { Enabled: true }
66+
# auto_wire_spec deliberately exercises wiring multiple instances of the
67+
# same backend, where numeric suffixes (storage1/storage2) read clearer
68+
# than any contrived semantic name.
69+
RSpec/IndexedLet:
70+
Exclude:
71+
- 'spec/storage/auto_wire_spec.rb'
6472
RSpec/MessageSpies: { Enabled: false }
6573
RSpec/MultipleExpectations: { Enabled: false }
6674
RSpec/MultipleMemoizedHelpers: { Enabled: false }
6775
RSpec/NamedSubject: { Enabled: false }
68-
RSpec/Rails/AvoidSetupHook: { Enabled: true }
69-
RSpec/Rails/HaveHttpStatus: { Enabled: true }
76+
RSpec/SpecFilePathFormat: { Enabled: false }
77+
RSpec/SpecFilePathSuffix: { Enabled: false }
7078
RSpec/SubjectDeclaration: { Enabled: true }
7179
RSpec/SubjectStub: { Enabled: false }
7280
RSpec/VerifiedDoubleReference: { Enabled: true }
@@ -80,6 +88,12 @@ Metrics/PerceivedComplexity: { Enabled: false }
8088

8189
Naming/BlockForwarding: { Enabled: true }
8290
Naming/MethodParameterName: { MinNameLength: 1 }
91+
# Storage interface methods like #open / #close / #reopen are imperative
92+
# actions, not predicates — they happen to return a boolean to indicate
93+
# success. Renaming them would break the public Storage::Interface contract.
94+
Naming/PredicateMethod:
95+
Exclude:
96+
- 'lib/faulty/storage/**/*.rb'
8397

8498
Security/CompoundHash: { Enabled: true }
8599
Security/IoMethods: { Enabled: true }
@@ -115,6 +129,11 @@ Style/NilLambda: { Enabled: true }
115129
Style/NumberedParameters: { Enabled: true }
116130
Style/NumberedParametersLimit: { Enabled: true }
117131
Style/ObjectThen: { Enabled: true }
132+
# Patch files monkey-patch upstream constants (Redis, Mysql2) and so
133+
# necessarily reopen multiple top-level modules in the same file.
134+
Style/OneClassPerFile:
135+
Exclude:
136+
- 'lib/faulty/patch/**/*.rb'
118137
Style/OpenStructUse: { Enabled: true }
119138
Style/QuotedSymbols: { Enabled: true }
120139
Style/RedundantArgument: { Enabled: true }

CHANGELOG.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,47 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99
[Unreleased]
1010
-------------------
1111

12+
[0.12.0] - 2026-05-13
13+
---------------------
14+
15+
Runtime behavior is unchanged from `0.11.0` — the version bump reflects
16+
support-policy and toolchain breaking changes only. Upgrading should be
17+
a drop-in replacement on any supported Ruby.
18+
19+
### Breaking
20+
21+
* Drop support for Ruby < 3.1. Ruby 2.3 – 3.0 are EOL upstream and are no
22+
longer covered by CI. Faulty now requires Ruby 3.1 or newer.
23+
* `faulty.gemspec` declares `required_ruby_version = '>= 3.1'`, so older
24+
Rubies will refuse to install the gem.
25+
26+
### Changed
27+
28+
* Modernize the CI matrix: Ruby `3.1`, `3.2`, `3.3`, `3.4`, `jruby-head`,
29+
and `truffleruby-head`; Redis 4 and 5; OpenSearch `2.19.5` (default) and
30+
`3.0.0`; Elasticsearch `7.17.x` for back-compat coverage. The
31+
Elasticsearch 7.17 row runs the `elasticsearch:7.17.28` Docker image
32+
(which ships a JDK that handles cgroupv2 on current runners) against
33+
the `elasticsearch ~> 7.17.11` gem.
34+
* Pass `DISABLE_INSTALL_DEMO_CONFIG=true` to the OpenSearch service
35+
container so OpenSearch 2.12+ doesn't require an admin-password env
36+
var just to boot up with the security plugin disabled.
37+
* Replace the deprecated `:mingw` / `:x64_mingw` platform symbols in the
38+
`Gemfile` with the unified `:windows` symbol Bundler now expects.
39+
* Upgrade development dependencies: `rubocop ~> 1.84`, `rubocop-rspec ~> 3.9`,
40+
`simplecov-cobertura ~> 3.1`, `opensearch-ruby ~> 3.4`.
41+
* `LogListener` now lazy-requires `logger` only when constructing the
42+
default `Logger.new($stderr)`. Consumers that pass their own logger or
43+
run under Rails do not need the stdlib `logger` gem on the load path.
44+
This keeps Faulty boot-clean on Ruby 3.5+ where `logger` is no longer a
45+
default gem.
46+
47+
### Removed
48+
49+
* Drop Redis 3 from the test matrix.
50+
* Drop the Ruby 2.3 carve-out in `spec/spec_helper.rb` that conditionally
51+
loaded the `mysql2` patch.
52+
1253
[0.11.0] - 2023-04-26
1354
---------------------
1455

Gemfile

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,28 +8,27 @@ gemspec
88
# here. This also allows us to use conditional dependencies that depend on the
99
# platform
1010

11-
not_jruby = %i[ruby mingw x64_mingw].freeze
11+
not_jruby = %i[ruby windows].freeze
1212

1313
gem 'activesupport', '>= 4.2'
1414
gem 'byebug', platforms: not_jruby
15+
gem 'honeybadger', '>= 2.0'
1516
gem 'irb', '~> 1.0'
17+
# stdlib `logger` became a bundled gem in Ruby 3.5. The runtime only needs
18+
# it when the default LogListener actually constructs a Logger; we pull it
19+
# in here so the test suite and `bin/benchmark` boot the default Faulty
20+
# pipeline without forcing it on consumers who supply their own listeners.
21+
gem 'logger'
1622
# Minimum of 0.5.0 for specific error classes
1723
gem 'mysql2', '>= 0.5.0', platforms: not_jruby
1824
gem 'redcarpet', '~> 3.5', platforms: not_jruby
1925
gem 'rspec_junit_formatter', '~> 0.4'
26+
gem 'rubocop', '~> 1.84'
27+
gem 'rubocop-rspec', '~> 3.9'
28+
gem 'simplecov', '>= 0.17.1'
29+
gem 'simplecov-cobertura', '~> 3.1'
2030
gem 'yard', '~> 0.9.25', platforms: not_jruby
2131

22-
if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.4')
23-
gem 'honeybadger', '>= 2.0'
24-
end
25-
26-
if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.6')
27-
gem 'rubocop', '~> 1.32.0'
28-
gem 'rubocop-rspec', '~> 2.12'
29-
gem 'simplecov', '>= 0.17.1'
30-
gem 'simplecov-cobertura', '~> 2.1'
31-
end
32-
3332
if ENV['REDIS_VERSION']
3433
gem 'redis', "~> #{ENV['REDIS_VERSION']}"
3534
end
@@ -39,5 +38,5 @@ if ENV['SEARCH_GEM']
3938
name = 'opensearch-ruby' if name == 'opensearch'
4039
gem name, "~> #{version}"
4140
else
42-
gem 'opensearch-ruby', '~> 2.1'
41+
gem 'opensearch-ruby', '~> 3.4'
4342
end

bin/benchmark

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,9 @@ Benchmark.bm(width) do |b|
1818

1919
b.report('memory storage failures') do
2020
n.times do
21-
begin
22-
in_memory.circuit(:memory_fail, sample_threshold: n + 1).run { raise 'fail' }
23-
rescue StandardError
24-
# Expected to raise here
25-
end
21+
in_memory.circuit(:memory_fail, sample_threshold: n + 1).run { raise 'fail' }
22+
rescue StandardError
23+
# Expected to raise here
2624
end
2725
end
2826

@@ -42,11 +40,9 @@ Benchmark.bm(width) do |b|
4240

4341
b.report('redis storage failures') do
4442
n.times do
45-
begin
46-
redis.circuit(:memory_fail, sample_threshold: n + 1).run { raise 'fail' }
47-
rescue StandardError
48-
# Expected to raise here
49-
end
43+
redis.circuit(:memory_fail, sample_threshold: n + 1).run { raise 'fail' }
44+
rescue StandardError
45+
# Expected to raise here
5046
end
5147
end
5248

faulty.gemspec

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ Gem::Specification.new do |spec|
2121
spec.files = Dir['lib/**/*.rb', '*.md', '*.txt', '.yardopts']
2222
spec.require_paths = ['lib']
2323

24-
spec.required_ruby_version = '>= 2.3'
24+
spec.required_ruby_version = '>= 3.1'
2525

26-
spec.add_runtime_dependency 'concurrent-ruby', '~> 1.0'
26+
spec.add_dependency 'concurrent-ruby', '~> 1.0'
2727

2828
# Only essential development tools and dependencies go here.
2929
# Other non-essential development dependencies go in the Gemfile.

lib/faulty.rb

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,12 @@ class << self
4141
# @param config [Hash] Attributes for {Faulty::Options}
4242
# @yield [Faulty::Options] For setting options in a block
4343
# @return [self]
44-
def init(default_name = :default, **config, &block)
44+
def init(default_name = :default, **config, &)
4545
raise AlreadyInitializedError if @instances
4646

4747
@default_instance = default_name
4848
@instances = Concurrent::Map.new
49-
register(default_name, new(**config, &block)) unless default_name.nil?
49+
register(default_name, new(**config, &)) unless default_name.nil?
5050
self
5151
rescue StandardError
5252
@instances = nil
@@ -110,8 +110,8 @@ def options
110110
# @param (see Faulty#circuit)
111111
# @yield (see Faulty#circuit)
112112
# @return (see Faulty#circuit)
113-
def circuit(name, **config, &block)
114-
default.circuit(name, **config, &block)
113+
def circuit(name, **config, &)
114+
default.circuit(name, **config, &)
115115
end
116116

117117
# Get a list of all circuit names for the default instance
@@ -236,8 +236,8 @@ def defaults
236236
# @see Options
237237
# @param options [Hash] Attributes for {Options}
238238
# @yield [Options] For setting options in a block
239-
def initialize(**options, &block)
240-
@options = Options.new(options, &block)
239+
def initialize(**options, &)
240+
@options = Options.new(options, &)
241241
@registry = CircuitRegistry.new(circuit_options)
242242
end
243243

@@ -252,9 +252,9 @@ def initialize(**options, &block)
252252
# @param options [Hash] Attributes for {Circuit::Options}
253253
# @yield [Circuit::Options] For setting options in a block
254254
# @return [Circuit] The new circuit or the existing circuit if it already exists
255-
def circuit(name, **options, &block)
255+
def circuit(name, **options, &)
256256
name = name.to_s
257-
@registry.retrieve(name, options, &block)
257+
@registry.retrieve(name, options, &)
258258
end
259259

260260
# Get a list of all circuit names
@@ -285,7 +285,7 @@ def clear!
285285
# @return [Hash] The circuit options
286286
def circuit_options
287287
@options.to_h
288-
.select { |k, _v| %i[cache storage notifier].include?(k) }
288+
.slice(:cache, :storage, :notifier)
289289
.merge(options.circuit_defaults)
290290
end
291291
end

0 commit comments

Comments
 (0)