Skip to content

Add Ruby 4.0 compatibility#2316

Merged
nesquena merged 5 commits into
masterfrom
ruby4
Mar 28, 2026
Merged

Add Ruby 4.0 compatibility#2316
nesquena merged 5 commits into
masterfrom
ruby4

Conversation

@nesquena
Copy link
Copy Markdown
Member

Summary

Fixes test suite to pass on Ruby 4.0 (and continues to pass on Ruby 3.x). Ruby 4.0 introduced several breaking changes that affected padrino-framework:

Changes

Frozen string fixes (padrino-core/lib/padrino-core/application/routing.rb)

  • Symbol#to_s now returns a frozen string in Ruby 4.0. Route paths derived from symbols (e.g. get :show) were being mutated in-place with gsub!, <<, etc., causing FrozenError. Fixed by calling .dup on the result of .to_s and on frozen string paths before mutation.
  • String literals are also frozen by default in Ruby 4.0. Changed new_url = '' to String.new in rebase_url.

Removed default gems

  • ostruct: Removed from Ruby 4.0 stdlib. Added as a runtime dependency in padrino-core.gemspec since padrino-core/configuration.rb requires it.
  • logger: Removed from Ruby 4.0 stdlib. Added to Gemfile (needed by rack-protection).

Replaced oga with nokogiri (padrino/test/padrino/test-methods.rb)

  • oga depends on ruby-ll, which has a C extension that fails to compile on Ruby 4.0. Replaced with nokogiri which is actively maintained and Ruby 4.0 compatible. Only used in test assertions.

Minitest mock extraction (padrino-core/test/helper.rb, padrino-cache/test/helper.rb)

  • Object#stub was extracted from minitest 6.0 into the separate minitest-mock gem. Added the gem and require 'minitest/mock' to test helpers that use .stub.

Logger encoding fix (padrino-core/lib/padrino-core/logger.rb, padrino-core/test/test_logger.rb)

  • Fixed encoding errors when writing binary/mixed-encoding data to the logger on Ruby 4.0, where StringIO defaults to US-ASCII. Binary data is now encoded to UTF-8 with replacement before writing.

Test Results (Ruby 4.0.2)

All 9 sub-gems pass: 1,441 runs, 5,465 assertions, 0 failures, 0 errors.

Replace Oga with Nokogiri and address frozen string compatibility

- Replace Oga dependency with Nokogiri for HTML parsing
- Add minitest-mock, logger, and ostruct as explicit dependencies
- Fix frozen string mutations by using String.new or .dup where needed
- Ensure UTF-8 encoding for StringIO in logger tests
- Update logger to handle encoding for non-UTF-8 strings when sanitize_encoding is not set
- Update test helper to use Nokogiri API (element['attr'] instead of element.get('attr'))
- Conditionally require minitest-mock gem only for Ruby >= 3.2
- Add version constraint for sqlite3 gem on Ruby < 3.1
- Wrap minitest/mock requires in rescue blocks for older minitest versions
- Reorder logger gem declaration for consistency
@nesquena
Copy link
Copy Markdown
Member Author

nesquena commented Mar 28, 2026

@achiurizo Can you review this pull request? I'm thinking of merging this in and then at some point releasing 0.16.2. Should allow the full Padrino test suite to be green across all Ruby versions from 2.7 to Ruby 4. Since this only affects test files, I'll wait to release another version to rubygems. But this fixes the build matrix

@nesquena nesquena requested a review from achiurizo March 28, 2026 00:36
Copy link
Copy Markdown
Member

@achiurizo achiurizo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — nice work getting Ruby 4.0 compat in. Left a few minor comments.

Comment thread padrino-core/lib/padrino-core/logger.rb Outdated
Comment on lines +448 to +449
else
line.encode!('UTF-8', invalid: :replace, undef: :replace) unless line.encoding == Encoding::UTF_8
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This changes behavior on Ruby 3.x too — binary/non-UTF-8 data that previously wrote through untouched will now get replacement characters when @sanitize_encoding isn't set.

Consider scoping to the log stream's encoding so it only kicks in when the write would actually fail:

Suggested change
else
line.encode!('UTF-8', invalid: :replace, undef: :replace) unless line.encoding == Encoding::UTF_8
line.encode!(@log.external_encoding || Encoding::UTF_8, invalid: :replace, undef: :replace) unless line.encoding == Encoding::UTF_8

Comment thread Gemfile
Comment thread Gemfile
…andling

- Remove Ruby version check for minitest-mock gem dependency
- Remove ostruct gem from development dependencies
- Update logger to use @log.external_encoding when sanitize_encoding is not set
- Ensure encoding compatibility with log device's external encoding
- Conditionally require minitest-mock gem only for Ruby >= 3.1
- Ensures compatibility with older Ruby versions where minitest-mock may not be needed
@nesquena
Copy link
Copy Markdown
Member Author

Thanks for the review Arthur! Applied all three suggestions, with one caveat:

Logger encoding — good catch on the behavior change for 3.x. Now uses @log.external_encoding || Encoding::UTF_8 so it only encodes when the write would actually fail against the stream's encoding.

minitest-mock — I tried removing the Ruby version guard, but minitest-mock resolves to 5.27.0, which declares required_ruby_version >= 3.1. Bundler fails at resolution time on Ruby 2.7/3.0 before the begin/rescue LoadError in the test helpers ever gets a chance to run. Kept the guard but adjusted it to if RUBY_VERSION >= '3.1' to match the gem's own requirement.

ostruct — removed the redundant Gemfile entry since it's already in the gemspec.

Pushed the updates, tests still green across all sub-gems. Going to merge as soon as all the tests pass green.

@nesquena nesquena merged commit a63de9a into master Mar 28, 2026
8 checks passed
@nesquena nesquena deleted the ruby4 branch March 28, 2026 00:51
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