Skip to content

Release 0.12.0: modernize toolchain and CI matrix#78

Merged
justinhoward merged 3 commits into
masterfrom
modernize
May 13, 2026
Merged

Release 0.12.0: modernize toolchain and CI matrix#78
justinhoward merged 3 commits into
masterfrom
modernize

Conversation

@justinhoward

@justinhoward justinhoward commented May 13, 2026

Copy link
Copy Markdown
Member

Why

Master's last green CI run was July 2025. Since then, GitHub Actions runner upgrades and dependency drift made the workflow effectively unrunnable: OpenSearch service containers fail to boot, simplecov-cobertura 2.1.0 produces a coverage report the new REXML parser rejects (so even passing test runs exit 1), and the Rubocop pin is two years stale. This blocked any PR (e.g. #70) from getting a clean signal.

The codebase has been functionally stable for a while, so rather than patching individual pins this branch modernizes the floor and ships the result as 0.12.0. Runtime behavior is unchanged from 0.11.0 — the version bump reflects the support-policy and toolchain breaking changes only (per the 0.x convention of bumping the minor on breaking changes). Upgrading should be a drop-in replacement on any supported Ruby.

What's in here

0.12.0 release prep

  • lib/faulty/version.rb -> 0.12.0.
  • CHANGELOG.md gets a [0.12.0] section with Breaking / Changed / Removed buckets calling out the Ruby support drop.
  • faulty.gemspec declares required_ruby_version = '>= 3.1', so older Rubies refuse to install the gem.

Runtime

  • Drop support for Ruby < 3.1 (2.3 - 3.0 are EOL upstream).
  • Lazy-require 'logger' in LogListener#initialize only when the fallback ::Logger.new($stderr) branch is actually taken. Ruby 3.5 extracts logger from default gems, and the eager require crashed boot with NameError on stock 3.3+. Pre-existing bug, surfaced once transitive requires went away.

CI matrix (.github/workflows/ci.yml)

  • Ruby: 3.1, 3.2, 3.3, 3.4, jruby-head, truffleruby-head.
  • OpenSearch: 2.19.5 default + a 3.0.0 row. Pass DISABLE_INSTALL_DEMO_CONFIG=true to the service container so OpenSearch 2.12+ doesn't refuse to boot waiting on OPENSEARCH_INITIAL_ADMIN_PASSWORD for a security demo we're immediately disabling anyway.
  • Elasticsearch: keep the 7.17.x back-compat row. Image is bumped to elasticsearch:7.17.28 (the JDK in the original 7.17.0 image NPEs against the cgroupv2 setup on current runners); gem floor is elasticsearch ~> 7.17.11, the latest 7.17 client. Drop the Elasticsearch 8.15.0 row entirely — the Faulty patch still references Elasticsearch::Transport::Transport::Error, and ES 8+ moved transport into the elastic-transport gem under Elastic::Transport. ES 8 support is a separate refactor, tracked as follow-up.
  • Redis: default redis gem 5, keep one 4 row, drop 3.
  • Actions: checkout@v4, codecov-action@v5. Toolchain jobs (rubocop / yard / check_version / release) move to Ruby 3.4.

Gemfile

  • Replace deprecated :mingw / :x64_mingw platform symbols with the unified :windows symbol Bundler now expects (the old form prints a [DEPRECATED] line at every bundle install).
  • Pin rubocop ~> 1.84 (was 1.32; the old version emitted a wall of deprecation warnings against rubocop-ast 1.49).
  • Pin rubocop-rspec ~> 3.9.
  • Pin simplecov-cobertura ~> 3.1 (the 2.x XML-without-root-element bug is fixed in 3.x).
  • Pin opensearch-ruby ~> 3.4 (was 2.1).
  • Add logger for dev/test/benchmark only.

Rubocop config

  • TargetRubyVersion: 3.1, NewCops: enable, migrate require: to plugins:.
  • Drop the now-extracted RSpec/Capybara/*, RSpec/Rails/*, RSpec/FactoryBot/* entries; replace RSpec/FilePath with the new RSpec/SpecFilePath{Format,Suffix} knobs.
  • Scope-exclude three new cops that misfire on intentional patterns: Naming/PredicateMethod (lib/faulty/storage/** - open/close/reopen are imperative interface methods that happen to return booleans; renaming would break Storage::Interface), Style/OneClassPerFile (lib/faulty/patch/** - monkey-patches reopen upstream constants), and RSpec/IndexedLet (auto_wire_spec.rb - numbered duplicate-backend fixtures read clearer than contrived names).
  • Disable Gemspec/DevelopmentDependencies (the gemspec intentionally splits essential vs non-essential dev deps with the Gemfile).

Autocorrects from rubocop -A (no behavior change)

  • &block -> anonymous & forwarding.
  • begin/rescue/end inside a method body -> method/block-level rescue.
  • ::Redis / ::Logger -> Redis / Logger (no namespace collisions).
  • **hash.merge(k: v) -> hash, k: v.
  • .select { |k,_v| %i[..].include?(k) } -> .slice(...).
  • add_runtime_dependency -> add_dependency.

Tiny spec touch-ups

  • Wrap three "nothing raises" tests in expect { ... }.not_to raise_error so they actually assert behavior (and silence RSpec/NoExpectationExample).
  • Drop the Ruby-2.3 mysql2 carve-out in spec_helper.rb.

Test plan

  • bundle exec rubocop - 81 files, 0 offenses.
  • bin/yardoc --fail-on-warning - 87% documented, no warnings.
  • bin/benchmark runs cleanly on this branch. Two-run averages show the in-memory / log-listener paths land ~2 - 3% faster than master (consistent direction across all four CPU-bound rows, plausibly from .slice and anonymous-block forwarding); Redis paths are within run-to-run noise.
  • Full matrix green on push.

Follow-up

  • PR Reserve half-open tests #70 ("Reserve half-open tests") still has a real Metrics/AbcSize offense in the new pipelined Redis#status. Easiest fix once this branch lands and Reserve half-open tests #70 rebases on top: extract the futures-fetch into a small helper. Not in scope here.
  • Elasticsearch 8.x client support. Requires reworking lib/faulty/patch/elasticsearch.rb to handle both the pre-8 Elasticsearch::Transport namespace and the 8.x Elastic::Transport namespace (in the split-out elastic-transport gem). Tracked separately so it doesn't gate this release.

Drop EOL Rubies (2.3 - 3.0) and bump the floor to Ruby 3.1. Refresh
the gem pins so CI resolves working versions:

* rubocop ~> 1.84 (was 1.32; was emitting deprecation noise against
  rubocop-ast 1.49 and the new release fixes that)
* rubocop-rspec ~> 3.9 (Capybara/FactoryBot/Rails cops were extracted
  in 3.0; .rubocop.yml updated to drop those entries and migrate
  `require:` to `plugins:`)
* simplecov-cobertura ~> 3.1 (2.1.0 produces XML without a root element
  which `rexml 3.4.2+` rejects, breaking CI even when specs pass)
* opensearch-ruby ~> 3.4

CI workflow:
* Pin matrix to Ruby 3.1 - 3.4 + jruby-head + truffleruby-head
* Replace OpenSearch 1.0.1 / 2.2.1 (the 2.2.1 image's bundled JDK NPEs
  on cgroupv2 under current ubuntu-latest runners) with 2.19.5 and a
  single 3.0.0 row
* Replace Elasticsearch 7.5.0 / 7.13.x with 7.17.0 (back-compat) and
  8.15.0; set xpack.security.enabled=false so the test client can use
  plain HTTP
* Drop Redis gem 3 from the matrix; default Redis 5, keep one Redis 4
* Bump actions: checkout@v4, codecov-action@v5
* Move rubocop/yard/check_version/release jobs to Ruby 3.4

Rubocop config:
* TargetRubyVersion: 3.1, NewCops: enable
* Scope-exclude Naming/PredicateMethod from lib/faulty/storage/**
  (open/close/reopen are interface actions, not predicates)
* Scope-exclude Style/OneClassPerFile from lib/faulty/patch/**
  (monkey-patches reopen upstream constants)
* Scope-exclude RSpec/IndexedLet from auto_wire_spec.rb
* Disable Gemspec/DevelopmentDependencies (intentional split with
  the Gemfile)

Source autocorrects via rubocop -A (no behaviour change):
* &block -> anonymous & forwarding
* begin/rescue/end inside method body -> method-level rescue
* ::Redis / ::Logger -> Redis / Logger
* **hash.merge(k: v) -> hash, k: v
* .select { |k,_v| %i[..].include?(k) } -> .slice(...)
* add_runtime_dependency -> add_dependency

Spec touch-ups: wrap the "nothing raises" tests in
expect { ... }.not_to raise_error so they actually assert behaviour
(and silence RSpec/NoExpectationExample). Drop the Ruby-2.3 mysql2
carve-out in spec_helper.rb.
Ruby 3.5 extracts `logger` from default gems, so callers who don't
otherwise pull it in see `NameError: uninitialized constant Logger` the
moment Faulty's default LogListener constructs its fallback. Move the
`require 'logger'` inside the only branch that actually uses it: when
no logger is passed and Rails isn't loaded. Consumers who supply their
own logger or run under Rails won't pay the require cost.

Add `logger` to the dev Gemfile so our specs and `bin/benchmark`, which
do exercise the default pipeline, boot cleanly on Ruby 3.3+.
@justinhoward justinhoward changed the title Modernize toolchain and CI matrix Release 1.0.0: modernize toolchain and CI matrix May 13, 2026
@justinhoward justinhoward mentioned this pull request May 13, 2026
5 tasks
@justinhoward justinhoward changed the title Release 1.0.0: modernize toolchain and CI matrix Release 0.12.0: modernize toolchain and CI matrix May 13, 2026
@justinhoward justinhoward merged commit 6bb0829 into master May 13, 2026
15 checks passed
@justinhoward justinhoward deleted the modernize branch May 13, 2026 20:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants