1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
|
# frozen_string_literal: true
require "cases/helper"
require "rack"
module ActiveRecord
module ConnectionAdapters
class ConnectionManagementTest < ActiveRecord::TestCase
self.use_transactional_tests = false
class App
attr_reader :calls
def initialize
@calls = []
end
def call(env)
@calls << env
[200, {}, ["hi mom"]]
end
end
def setup
@env = {}
@app = App.new
@management = middleware(@app)
# make sure we have an active connection
assert ActiveRecord::Base.lease_connection
assert ActiveRecord::Base.connection_handler.active_connections?(:all)
end
def test_app_delegation
manager = middleware(@app)
manager.call @env
assert_equal [@env], @app.calls
end
def test_body_responds_to_each
_, _, body = @management.call(@env)
bits = []
body.each { |bit| bits << bit }
assert_equal ["hi mom"], bits
end
def test_connections_are_cleared_after_body_close
_, _, body = @management.call(@env)
body.close
assert_not ActiveRecord::Base.connection_handler.active_connections?(:all)
end
test "connections are cleared even if inside a non-joinable transaction" do
ActiveRecord::Base.connection_pool.pin_connection!(Thread.current)
Thread.new do
assert ActiveRecord::Base.lease_connection
assert ActiveRecord::Base.connection_handler.active_connections?(:all)
_, _, body = @management.call(@env)
body.close
assert_not ActiveRecord::Base.connection_handler.active_connections?(:all)
end.join
ensure
ActiveRecord::Base.connection_pool.unpin_connection!
end
def test_active_connections_are_not_cleared_on_body_close_during_transaction
ActiveRecord::Base.transaction do
_, _, body = @management.call(@env)
body.close
assert ActiveRecord::Base.connection_handler.active_connections?(:all)
end
end
def test_connections_closed_if_exception
app = Class.new(App) { def call(env); raise NotImplementedError; end }.new
explosive = middleware(app)
assert_raises(NotImplementedError) { explosive.call(@env) }
assert_not ActiveRecord::Base.connection_handler.active_connections?(:all)
end
def test_connections_not_closed_if_exception_inside_transaction
ActiveRecord::Base.transaction do
app = Class.new(App) { def call(env); raise RuntimeError; end }.new
explosive = middleware(app)
assert_raises(RuntimeError) { explosive.call(@env) }
assert ActiveRecord::Base.connection_handler.active_connections?(:all)
end
end
test "cancel asynchronous queries if an exception is raised" do
unless ActiveRecord::Base.lease_connection.supports_concurrent_connections?
skip "This adapter doesn't support asynchronous queries"
end
app = Class.new(App) do
attr_reader :future_result
def call(env)
@future_result = ActiveRecord::Base.lease_connection.select_all("SELECT * FROM does_not_exists", async: true)
raise NotImplementedError
end
end.new
explosive = middleware(app)
assert_raises(NotImplementedError) { explosive.call(@env) }
assert_raises FutureResult::Canceled do
app.future_result.to_a
end
end
test "doesn't clear active connections when running in a test case" do
executor.wrap do
@management.call(@env)
assert ActiveRecord::Base.connection_handler.active_connections?(:all)
end
end
test "proxy is polite to its body and responds to it" do
body = Class.new(String) { def to_path; "/path"; end }.new
app = lambda { |_| [200, {}, body] }
response_body = middleware(app).call(@env)[2]
assert_respond_to response_body, :to_path
assert_equal "/path", response_body.to_path
end
test "doesn't mutate the original response" do
original_response = [200, {}, "hi"]
app = lambda { |_| original_response }
middleware(app).call(@env)[2]
assert_equal "hi", original_response.last
end
private
def executor
@executor ||= Class.new(ActiveSupport::Executor).tap do |exe|
ActiveRecord::QueryCache.install_executor_hooks(exe)
ActiveRecord::AsynchronousQueriesTracker.install_executor_hooks(exe)
ActiveRecord::ConnectionAdapters::ConnectionPool.install_executor_hooks(exe)
end
end
def middleware(app)
lambda do |env|
a, b, c = executor.wrap { app.call(env) }
[a, b, Rack::BodyProxy.new(c) { }]
end
end
end
end
end
|