Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix error backtrace formatting on Ruby 3.4+ #1771

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
ruby: ['3.0', '3.1', '3.2', '3.3']
ruby: ['3.0', '3.1', '3.2', '3.3', '3.4']
luke-hill marked this conversation as resolved.
Show resolved Hide resolved
include:
- os: ubuntu-latest
ruby: jruby-9.4
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ that need to rely on procedural loading / reloading of files should use method i

### Fixed
- Fixed an issue where a change to one example in compatibility testing wasn't fully adhered to ([luke-hill](https://github.com/luke-hill))
- Fixed Ruby 3.4+ issue where error backtraces weren't being formatted. ([#1771](https://github.com/cucumber/cucumber-ruby/pull/1771) [orien](https://github.com/orien))

### Removed
- `StepDefinitionLight` associated methods. The class itself is present but deprecated
Expand Down
4 changes: 2 additions & 2 deletions features/docs/extending_cucumber/custom_formatter.feature
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ Feature: Custom Formatter
def initialize(config, options)
@io = config.out_stream
config.on_event :test_run_finished do |event|
@io.print options.inspect
@io.print options.to_json
Copy link
Contributor Author

Choose a reason for hiding this comment

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

In Ruby 3.4 the Hash#inspect method produces a different format:

Ruby 3.4:

irb:001> { a: 1 }.inspect
=> "{a: 1}"
irb:002> { 'a' => 1 }.inspect
=> "{\"a\" => 1}"
irb:003> { 2 => 1 }.inspect
=> "{2 => 1}"

Ruby 3.3

irb:001> { a: 1 }.inspect
=> "{:a=>1}"
irb:002> { 'a' => 1 }.inspect
=> "{\"a\"=>1}"
irb:003> { 2 => 1 }.inspect
=> "{2=>1}"

Let's change the test to use to_json instead of inspect. This way the test expectations will be consistent across all versions of Ruby in the CI build matrix.

Copy link
Contributor

Choose a reason for hiding this comment

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

Good find. I know this is the issue mainly in the main branch with regards to errors, nice that to_json gives us a quick fix

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm unresolving this so we know to look here for documentation purposes as/when a v10 is cut.

For now as part of this PR can you add some notes here: https://github.com/cucumber/cucumber-ruby/blob/main/upgrading_notes/10.0.0.md - if you are confident in doing so. Or if you'd prefer I can do it once this PR is merged. I'm flexible.

Copy link
Contributor Author

@orien orien Jan 8, 2025

Choose a reason for hiding this comment

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

I'm not sure what you're referring to specifically. This is a test/doc file. The changes here shouldn't affect downstream users.

end
end
end
Expand All @@ -54,5 +54,5 @@ Feature: Custom Formatter
When I run `cucumber features/f.feature --format MyCustom::Formatter,foo=bar,one=two --publish-quiet`
Then it should pass with exactly:
"""
{"foo"=>"bar", "one"=>"two"}
{"foo":"bar","one":"two"}
"""
4 changes: 3 additions & 1 deletion lib/cucumber/formatter/backtrace_filter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,10 @@ def exception

if ::ENV['CUCUMBER_TRUNCATE_OUTPUT']
# Strip off file locations
regexp = RUBY_VERSION >= '3.4' ? /(.*):in '/ : /(.*):in `/
filtered = filtered.map do |line|
line =~ /(.*):in `/ ? Regexp.last_match(1) : line
luke-hill marked this conversation as resolved.
Show resolved Hide resolved
match = regexp.match(line)
match ? match[1] : line
end
end

Expand Down
13 changes: 11 additions & 2 deletions lib/cucumber/glue/invoke_in_world.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ def self.replace_instance_exec_invocation_line!(backtrace, instance_exec_invocat
return unless instance_exec_pos

replacement_line = instance_exec_pos + INSTANCE_EXEC_OFFSET
backtrace[replacement_line].gsub!(/`.*'/, "`#{pseudo_method}'") if pseudo_method
if pseudo_method
pattern = RUBY_VERSION >= '3.4' ? /'.*'/ : /`.*'/
backtrace[replacement_line].gsub!(pattern, "`#{pseudo_method}'")
end

depth = backtrace.count { |line| line == instance_exec_invocation_line }
end_pos = depth > 1 ? instance_exec_pos : -1
Expand Down Expand Up @@ -49,7 +52,13 @@ def self.cucumber_compatible_arity?(args, block)
def self.cucumber_run_with_backtrace_filtering(pseudo_method)
yield
rescue Exception => e
instance_exec_invocation_line = "#{__FILE__}:#{__LINE__ - 2}:in `cucumber_run_with_backtrace_filtering'"
luke-hill marked this conversation as resolved.
Show resolved Hide resolved
yield_line_number = __LINE__ - 2
instance_exec_invocation_line =
if RUBY_VERSION >= '3.4'
"#{__FILE__}:#{yield_line_number}:in '#{name}.#{__method__}'"
else
"#{__FILE__}:#{yield_line_number}:in `#{__method__}'"
end
replace_instance_exec_invocation_line!((e.backtrace || []), instance_exec_invocation_line, pseudo_method)
raise e
end
Expand Down
33 changes: 18 additions & 15 deletions spec/cucumber/glue/proto_world_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -68,36 +68,39 @@ module Glue

define_steps do
When('an object is logged') do
log(a: 1, b: 2, c: 3)
object = Object.new
def object.to_s
'<test-object>'
end
log(object)
end
end

it 'attached the styring version on the object' do
expect(@out.string).to include '{:a=>1, :b=>2, :c=>3}'
it 'prints the stringified version of the object as a log message' do
expect(@out.string).to include('<test-object>')
end
end

describe 'when logging multiple items on one call' do
define_feature <<-FEATURE
Feature: Banana party
Feature: Logging multiple entries

Scenario: Monkey eats banana
When monkey eats banana
Scenario: Logging multiple entries
When logging multiple entries
FEATURE

define_steps do
When('{word} {word} {word}') do |subject, verb, complement|
log "subject: #{subject}", "verb: #{verb}", "complement: #{complement}", subject: subject, verb: verb, complement: complement
When('logging multiple entries') do
log 'entry one', 'entry two', 'entry three'
end
end

it 'logs each parameter independently' do
expect(@out.string).to include [
' subject: monkey',
' verb: eats',
' complement: banana',
' {:subject=>"monkey", :verb=>"eats", :complement=>"banana"}'
].join("\n")
it 'logs each entry independently' do
luke-hill marked this conversation as resolved.
Show resolved Hide resolved
expect(@out.string).to include([
' entry one',
' entry two',
' entry three'
].join("\n"))
end
end

Expand Down
Loading