File: connection_management_test.rb

package info (click to toggle)
rails 2%3A7.2.2.1%2Bdfsg-7
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 43,352 kB
  • sloc: ruby: 349,799; javascript: 30,703; yacc: 46; sql: 43; sh: 29; makefile: 27
file content (151 lines) | stat: -rw-r--r-- 4,989 bytes parent folder | download
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