Skip to content

Commit f1a4fd9

Browse files
committed
Specs for activerecord patch
[#46141013] Spec the desired behavior for ConnectionPool prior to removing the patch to sync with upstream 3.2.12.
1 parent 0f6b053 commit f1a4fd9

File tree

4 files changed

+306
-25
lines changed

4 files changed

+306
-25
lines changed

Rakefile

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,19 @@ require 'bundler/setup'
22

33
require 'metasploit_data_models'
44

5+
pathname = Pathname.new(__FILE__)
6+
root = pathname.parent
7+
8+
# add metasploit-framework/lib to load paths so rake files can just require
9+
# files normally without having to use __FILE__ and recalculating root and the
10+
# path to lib
11+
lib_pathname = root.join('lib')
12+
$LOAD_PATH.unshift(lib_pathname.to_s)
13+
514
#
615
# load rake files like a rails engine
716
#
817

9-
pathname = Pathname.new(__FILE__)
10-
root = pathname.parent
1118
rakefile_glob = root.join('lib', 'tasks', '**', '*.rake').to_path
1219

1320
Dir.glob(rakefile_glob) do |rakefile|

lib/metasploit/framework.rb

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Top-level namespace that is shared between {Metasploit::Framework
2+
# metasploit-framework} and pro, which uses Metasploit::Pro.
3+
module Metasploit
4+
# Supports Rails and Rails::Engine like access to metasploit-framework so it
5+
# works in compatible manner with activerecord's rake tasks and other
6+
# railties.
7+
module Framework
8+
# Returns the environment for {Metasploit::Framework}. Checks
9+
# `METASPLOIT_FRAMEWORK_ENV` environment variable for value. Defaults to
10+
# `'development'` if `METASPLOIT_FRAMEWORK_ENV` is not set in the
11+
# environment variables.
12+
#
13+
# {env} is a ActiveSupport::StringInquirer like `Rails.env` so it can be
14+
# queried for its value.
15+
#
16+
# @example check if environment is development
17+
# if Metasploit::Framework.env.development?
18+
# # runs only when in development
19+
# end
20+
#
21+
# @return [ActiveSupport::StringInquirer] the environment name
22+
def self.env
23+
unless instance_variable_defined? :@env
24+
name = ENV['METASPLOIT_FRAMEWORK_ENV']
25+
name ||= 'development'
26+
@env = ActiveSupport::StringInquirer.new(name)
27+
end
28+
29+
@env
30+
end
31+
32+
# Returns the root of the metasploit-framework project. Use in place of
33+
# `Rails.root`.
34+
#
35+
# @return [Pathname]
36+
def self.root
37+
unless instance_variable_defined? :@root
38+
pathname = Pathname.new(__FILE__)
39+
@root = pathname.parent.parent.parent
40+
end
41+
42+
@root
43+
end
44+
end
45+
end

lib/tasks/database.rake

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,6 @@
11
load 'active_record/railties/databases.rake'
22

3-
module Metasploit
4-
module Framework
5-
def self.env
6-
unless instance_variable_defined? :@env
7-
name = ENV['METASPLOIT_FRAMEWORK_ENV']
8-
name ||= 'development'
9-
@env = ActiveSupport::StringInquirer.new(name)
10-
end
11-
12-
@env
13-
end
14-
15-
def self.root
16-
unless instance_variable_defined? :@root
17-
pathname = Pathname.new(__FILE__)
18-
@root = pathname.parent.parent.parent
19-
end
20-
21-
@root
22-
end
23-
end
24-
end
25-
3+
require 'metasploit/framework'
264

275
# A modification to remove dependency on Rails.env
286
#
Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
require 'spec_helper'
2+
3+
# include patch under test
4+
require 'msf/core/patches/active_record'
5+
# helps with environment configuration to use for connection to database
6+
require 'metasploit/framework'
7+
8+
# load Mdm::Host for testing
9+
MetasploitDataModels.require_models
10+
11+
describe ActiveRecord::ConnectionAdapters::ConnectionPool do
12+
def database_configurations
13+
YAML.load_file(database_configurations_pathname)
14+
end
15+
16+
def database_configurations_pathname
17+
Metasploit::Framework.root.join('config', 'database.yml')
18+
end
19+
20+
subject(:connection_pool) do
21+
ActiveRecord::Base.connection_pool
22+
end
23+
24+
# Not all specs require a database connection, and railties aren't being
25+
# used, so have to manually establish connection.
26+
before(:each) do
27+
ActiveRecord::Base.configurations = database_configurations
28+
spec = ActiveRecord::Base.configurations[Metasploit::Framework.env]
29+
ActiveRecord::Base.establish_connection(spec)
30+
end
31+
32+
after(:each) do
33+
ActiveRecord::Base.clear_all_connections!
34+
end
35+
36+
context '#active_thread_connection?' do
37+
subject(:active_thread_connection?) do
38+
connection_pool.active_thread_connection?
39+
end
40+
41+
# Let! so that Thread is captured before creating and entering new Threads
42+
let!(:main_thread) do
43+
Thread.current
44+
end
45+
46+
before(:each) do
47+
ActiveRecord::Base.connection_pool.connection
48+
end
49+
50+
context 'with connection id' do
51+
subject(:active_thread_connection?) do
52+
connection_pool.active_thread_connection?(connection_id)
53+
end
54+
55+
context 'with connection id from other thread that has connection' do
56+
let(:connection_id) do
57+
main_thread.object_id
58+
end
59+
60+
it 'should be true' do
61+
thread = Thread.new do
62+
Thread.current.should_not == main_thread
63+
64+
expect(active_thread_connection?).to be_true
65+
end
66+
67+
thread.join
68+
end
69+
end
70+
end
71+
72+
context 'without connection id' do
73+
context 'in thread with connection' do
74+
it { should be_true }
75+
end
76+
77+
context 'in thread without connection' do
78+
it 'should be false' do
79+
thread = Thread.new do
80+
Thread.current.should_not == main_thread
81+
expect(active_thread_connection?).to be_false
82+
end
83+
84+
thread.join
85+
end
86+
end
87+
end
88+
end
89+
90+
context 'checkout' do
91+
92+
end
93+
94+
context '#with_connection' do
95+
def reserved_connection_count
96+
connection_pool.instance_variable_get(:@reserved_connections).length
97+
end
98+
99+
let(:connection_id) do
100+
main_thread.object_id
101+
end
102+
103+
it 'should call #current_connection_id' do
104+
connection_pool.should_receive(
105+
:current_connection_id
106+
).at_least(
107+
:once
108+
).and_call_original
109+
110+
connection_pool.with_connection { }
111+
end
112+
113+
it 'should call #active_thread_connection? with #current_connection_id' do
114+
current_connection_id = mock('Connection ID')
115+
connection_pool.stub(:current_connection_id => current_connection_id)
116+
117+
connection_pool.should_receive(
118+
:active_thread_connection?
119+
).with(
120+
current_connection_id
121+
).and_call_original
122+
123+
connection_pool.with_connection { }
124+
end
125+
126+
it 'should yield #connection' do
127+
connection = mock('Connection')
128+
connection_pool.stub(:connection => connection)
129+
130+
expect { |block|
131+
connection_pool.with_connection(&block)
132+
}.to yield_with_args(connection)
133+
end
134+
135+
context 'with active thread connection' do
136+
let!(:connection) do
137+
connection_pool.connection
138+
end
139+
140+
after(:each) do
141+
connection_pool.checkin connection
142+
end
143+
144+
it 'should return true from #active_thread_connection?' do
145+
expect(connection_pool.active_thread_connection?).to be_true
146+
end
147+
148+
context 'with error' do
149+
it 'should not release connection' do
150+
expect {
151+
# capture error so it doesn't stop example
152+
expect {
153+
connection_pool.with_connection do
154+
# raise error to trigger with_connection's ensure
155+
raise ArgumentError, 'bad arguments'
156+
end
157+
}.to raise_error(ArgumentError)
158+
}.to change {
159+
reserved_connection_count
160+
}.by(0)
161+
end
162+
end
163+
164+
context 'without error' do
165+
it 'should not release connection' do
166+
expect {
167+
connection_pool.with_connection { }
168+
}.to change{
169+
reserved_connection_count
170+
}.by(0)
171+
end
172+
end
173+
end
174+
175+
context 'without active thread connection' do
176+
it 'should return false from #active_thread_connection?' do
177+
expect(connection_pool.active_thread_connection?).to be_false
178+
end
179+
180+
context 'with error' do
181+
it 'should not leave connection created for block' do
182+
expect {
183+
# capture error so it doesn't stop example
184+
expect {
185+
connection_pool.with_connection do
186+
# raise error to trigger with_connection's ensure
187+
raise ArgumentError, 'bad arguments'
188+
end
189+
}.to raise_error(ArgumentError)
190+
}.to change {
191+
reserved_connection_count
192+
}.by(0)
193+
end
194+
end
195+
196+
context 'without error' do
197+
it 'should not leave connection created for block' do
198+
expect {
199+
connection_pool.with_connection { }
200+
}.to change{
201+
reserved_connection_count
202+
}.by(0)
203+
end
204+
end
205+
206+
context 'with nested' do
207+
it 'should not reserve another connection in the nested block' do
208+
before_count = reserved_connection_count
209+
210+
connection_pool.with_connection do
211+
child_count = reserved_connection_count
212+
count_change = child_count - before_count
213+
214+
count_change.should == 1
215+
216+
connection_pool.with_connection do
217+
grandchild_count = reserved_connection_count
218+
219+
grandchild_count.should == child_count
220+
end
221+
end
222+
223+
after_count = reserved_connection_count
224+
225+
after_count.should == before_count
226+
end
227+
end
228+
229+
context 'without with_connection first' do
230+
it 'should use connection reserved outside with_connection' do
231+
# Using query methods without a block is expected to retain the
232+
# reserved connection
233+
expect {
234+
# access database outside with_connection block
235+
Mdm::Host.count
236+
}.to change {
237+
reserved_connection_count
238+
}.by(1)
239+
240+
outside = reserved_connection_count
241+
242+
connection_pool.with_connection do
243+
inside = reserved_connection_count
244+
245+
inside.should == outside
246+
end
247+
end
248+
end
249+
end
250+
end
251+
end

0 commit comments

Comments
 (0)