Rack >= 2.2.2 tests skip `Rack::Events#on_finish`
Closed this issue ยท 3 comments
While working on migrating the OTel Instrumentation from using Rack middleware to the Events API we ran into some test failures starting with Rack >= 2.2.2.
It seems that the block passed into the EventedBodyProxy
that contains a call to on_finish
is no longer being executed during test runs.
https://github.com/open-telemetry/opentelemetry-ruby-contrib/actions/runs/4458505381/jobs/7834300742
There do not appear to be any changes between Rack 2.2.2 and 2.2.1 that would impact this behavior, but I do not think I see any buffered response tests in rack-test
so its unclear if there is some incompatibility I may have glossed over.
require 'bundler/inline'
gemfile do
source 'https://rubygems.org'
gem 'minitest'
gem 'rack', '2.2.2'
gem 'rack-test', '2.1.0'
end
require 'minitest/autorun'
require 'rack/test'
class MyEventHandler
include ::Rack::Events::Abstract
MyError = Class.new(StandardError)
def on_start(_, _)
$stderr.puts __method__
end
def on_send(_, _)
$stderr.puts __method__
end
def on_commit(_, _)
$stderr.puts __method__
end
def on_finish(_, _)
raise MyError, 'unexpected'
end
def on_error(_, _, _)
$stderr.puts __method__
end
end
class BufferedTest < MiniTest::Test
include Rack::Test::Methods
def app
Rack::Builder.new.tap do |builder|
builder.use Rack::Events, [MyEventHandler.new]
builder.run ->(_env) { [200, { 'content-type' => 'text/plain' }, Rack::BodyProxy.new('All responses are OK') { puts 'Hello world' }] }
end
end
def test_root
assert_raises MyEventHandler::MyError do
get '/'
end
end
end
> rack-test-failure(:|โ) %
๐ค ruby test.rb
Run options: --seed 35637
# Running:
on_start
on_commit
F
Finished in 0.004877s, 205.0441 runs/s, 205.0441 assertions/s.
1) Failure:
BufferedTest#test_root [test.rb:50]:
MyEventHandler::MyError expected but nothing was raised.
1 runs, 1 assertions, 1 failures, 0 errors, 0 skips
Any suggestions or thoughts as to why we may be running into this problem?
For comparison with Rack 2.2.1 and Rack-Test 2.1.0
require 'bundler/inline'
gemfile do
source 'https://rubygems.org'
gem 'minitest'
gem 'rack', '2.2.1'
gem 'rack-test', '2.1.0'
end
require 'minitest/autorun'
require 'rack/test'
class MyEventHandler
include ::Rack::Events::Abstract
MyError = Class.new(StandardError)
def on_start(_, _)
$stderr.puts __method__
end
def on_send(_, _)
$stderr.puts __method__
end
def on_commit(_, _)
$stderr.puts __method__
end
def on_finish(_, _)
raise MyError, 'unexpected'
end
def on_error(_, _, _)
$stderr.puts __method__
end
end
class BufferedTest < MiniTest::Test
include Rack::Test::Methods
def app
Rack::Builder.new.tap do |builder|
builder.use Rack::Events, [MyEventHandler.new]
builder.run ->(_env) { [200, { 'content-type' => 'text/plain' }, Rack::BodyProxy.new('All responses are OK') { puts 'Hello world' }] }
end
end
def test_root
assert_raises MyEventHandler::MyError do
get '/'
end
end
end
vibora ~/github/rack-test-failure(:|โ) %
๐ค ruby test.rb
Run options: --seed 43962
# Running:
on_start
on_commit
Hello world
.
Finished in 0.005103s, 195.9632 runs/s, 195.9632 assertions/s.
1 runs, 1 assertions, 0 failures, 0 errors, 0 skips
It helps if you pass Rack::BodyProxy
a valid rack body :). Change to Rack::BodyProxy.new(['All responses are OK'])
and the test works.
As to why the behavior is skipped for the invalid body, because it is a plain string (in a wrapper that still responds to to_str
), Rack::Response
treats it as a plain string, and implicitly wraps it in its own array, so the on_finish parts are never called. Potentially, you can consider that a bug in rack, but as you can only trigger it by providing an invalid rack response, it is debatable whether it is a bug or not. However, it's definitely not a bug in rack-test, so I'm closing this.
Thanks @jeremyevans!!! I should have been more diligent in verifying my code was correct so I appreciate you taking the time to review this.
๐๐ผ