Skip to content

Commit

Permalink
Log caller for each Thread.new for rake spec
Browse files Browse the repository at this point in the history
MSP-11147
  • Loading branch information
limhoff-r7 committed Nov 5, 2014
1 parent 96990fd commit 097aa33
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 30 deletions.
1 change: 1 addition & 0 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ require 'metasploit/framework/spec/untested_payloads'
Metasploit::Framework::Require.optionally_active_record_railtie

Metasploit::Framework::Application.load_tasks
Metasploit::Framework::Spec::Threads::Suite.define_task
Metasploit::Framework::Spec::UntestedPayloads.define_task
23 changes: 23 additions & 0 deletions lib/metasploit/framework/spec/threads/logger.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
require 'metasploit/framework/spec/threads/suite'

original_thread_new = Thread.method(:new)

# Patches `Thread.new` so that if logs `caller` so thread leaks can be traced
Thread.define_singleton_method(:new) { |*args, &block|
lines = ['BEGIN Thread.new caller']

caller.each do |frame|
lines << " #{frame}"
end

lines << 'END Thread.new caller'

Metasploit::Framework::Spec::Threads::Suite::LOG_PATHNAME.parent.mkpath

Metasploit::Framework::Spec::Threads::Suite::LOG_PATHNAME.open('a') { |f|
# single puts so threads can't write in between each other.
f.puts lines.join("\n")
}

original_thread_new.call(*args, &block)
}
90 changes: 60 additions & 30 deletions lib/metasploit/framework/spec/threads/suite.rb
Original file line number Diff line number Diff line change
@@ -1,35 +1,65 @@
module Metasploit::Framework::Spec::Threads::Suite
#
# CONSTANTS
#

EXPECTED_THREAD_COUNT_BEFORE_SUITE = 1

#
# Module Methods
#

# Configures `before(:suite)` and `after(:suite)` callback to detect thread leaks.
#
# @return [void]
def self.configure!
unless @configured
RSpec.configure do |config|
config.before(:suite) do
thread_count = Thread.list.count

expect(thread_count).to(
(be <= EXPECTED_THREAD_COUNT_BEFORE_SUITE),
"#{thread_count} #{'thread'.pluralize(thread_count)} exist(s) when " \
"only #{EXPECTED_THREAD_COUNT_BEFORE_SUITE} #{'thread'.pluralize(EXPECTED_THREAD_COUNT_BEFORE_SUITE)} " \
"expected before suite runs"
)
require 'pathname'

# @note needs to use explicit nesting. so this file can be loaded directly without loading 'metasploit/framework' which
# allows for faster loading of rake tasks.
module Metasploit
module Framework
module Spec
module Threads
module Suite
#
# CONSTANTS
#

# Number of allowed threads when threads are counted in `before(:suite)`
EXPECTED_THREAD_COUNT_BEFORE_SUITE = 1
# `caller` for all Thread.new calls
LOG_PATHNAME = Pathname.new('log/metasploit/framework/spec/threads/suite.log')

#
# Module Methods
#

# Configures `before(:suite)` and `after(:suite)` callback to detect thread leaks.
#
# @return [void]
def self.configure!
unless @configured
RSpec.configure do |config|
config.before(:suite) do
thread_count = Thread.list.count

# check with if first so that error message can be constructed lazily
if thread_count > EXPECTED_THREAD_COUNT_BEFORE_SUITE
log = LOG_PATHNAME.read()

raise RuntimeError,
"#{thread_count} #{'thread'.pluralize(thread_count)} exist(s) when " \
"only #{EXPECTED_THREAD_COUNT_BEFORE_SUITE} " \
"#{'thread'.pluralize(EXPECTED_THREAD_COUNT_BEFORE_SUITE)} expected before suite runs:\n#{log}"
end
end
end

@configured = true
end

@configured
end

def self.define_task
Rake::Task.define_task('metasploit:framework:spec:threads:suite') do
parent_pathname = Pathname.new(__FILE__).parent
threads_logger_pathname = parent_pathname.join('logger')
load_pathname = parent_pathname.parent.parent.parent.parent.expand_path

ENV['RUBYOPT'] = "-I#{load_pathname} -r#{threads_logger_pathname} #{ENV['RUBYOPT']}"
end

Rake::Task.define_task(spec: 'metasploit:framework:spec:threads:suite')
end
end
end

@configured = true
end

@configured
end
end

0 comments on commit 097aa33

Please sign in to comment.