Module: Metasploit::Framework::Spec::Threads::Suite
- Defined in:
- lib/metasploit/framework/spec/threads/suite.rb
Constant Summary collapse
- EXPECTED_THREAD_COUNT_AROUND_SUITE =
Number of allowed threads when threads are counted in `after(:suite)` or `before(:suite)`
ENV['REMOTE_DB'] ? 4 : 3
- LOG_PATHNAME =
`caller` for all Thread.new calls
Pathname.new('log/metasploit/framework/spec/threads/suite.log')
- UUID_REGEXP =
Regular expression for extracting the UUID out of LOG_PATHNAME for each Thread.new caller block
/BEGIN Thread.new caller \((?<uuid>.*)\)/
- UUID_THREAD_LOCAL_VARIABLE =
Name of thread local variable that Thread UUID is stored
"metasploit/framework/spec/threads/logger/uuid"
Class Method Summary collapse
-
.caller_by_thread_uuid ⇒ Hash{String => Array<String>}
The `caller` for each Thread UUID.
-
.configure! ⇒ void
Configures `before(:suite)` and `after(:suite)` callback to detect thread leaks.
- .define_task ⇒ Object
-
.each_suite_line {|line| ... } ⇒ Object
Yields each line of LOG_PATHNAME that happened during the suite run.
-
.each_thread_line {|uuid, line| ... } ⇒ Object
Yield each line for each Thread UUID gathered during the suite run.
- .non_debugger_thread_list ⇒ Object
Class Method Details
.caller_by_thread_uuid ⇒ Hash{String => Array<String>}
The `caller` for each Thread UUID.
191 192 193 194 195 196 197 198 199 200 201 |
# File 'lib/metasploit/framework/spec/threads/suite.rb', line 191 def self.caller_by_thread_uuid lines_by_thread_uuid = Hash.new { |hash, uuid| hash[uuid] = [] } each_thread_line do |uuid, line| lines_by_thread_uuid[uuid] << line end lines_by_thread_uuid end |
.configure! ⇒ void
This method returns an undefined value.
Configures `before(:suite)` and `after(:suite)` callback to detect thread leaks.
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 |
# File 'lib/metasploit/framework/spec/threads/suite.rb', line 31 def self.configure! unless @configured RSpec.configure do |config| config.before(:suite) do thread_count = Metasploit::Framework::Spec::Threads::Suite.non_debugger_thread_list.count # check with if first so that error message can be constructed lazily if thread_count > EXPECTED_THREAD_COUNT_AROUND_SUITE # LOG_PATHNAME may not exist if suite run without `rake spec` if LOG_PATHNAME.exist? log = LOG_PATHNAME.read() else log "Run `rake spec` to log where Thread.new is called." end raise RuntimeError, "#{thread_count} #{'thread'.pluralize(thread_count)} exist(s) when " \ "only #{EXPECTED_THREAD_COUNT_AROUND_SUITE} " \ "#{'thread'.pluralize(EXPECTED_THREAD_COUNT_AROUND_SUITE)} expected before suite runs:\n" \ "#{log}" end LOG_PATHNAME.parent.mkpath LOG_PATHNAME.open('a') do |f| # separator so after(:suite) can differentiate between threads created before(:suite) and during the # suites f.puts 'before(:suite)' end end config.after(:suite) do LOG_PATHNAME.parent.mkpath LOG_PATHNAME.open('a') do |f| # separator so that a flip flop can be used when reading the file below. Also useful if it turns # out any threads are being created after this callback, which could be the case if another # after(:suite) accidentally created threads by creating an Msf::Simple::Framework instance. f.puts 'after(:suite)' end thread_list = Metasploit::Framework::Spec::Threads::Suite.non_debugger_thread_list thread_count = thread_list.count if thread_count > EXPECTED_THREAD_COUNT_AROUND_SUITE error_lines = [] if LOG_PATHNAME.exist? caller_by_thread_uuid = Metasploit::Framework::Spec::Threads::Suite.caller_by_thread_uuid thread_list.each do |thread| thread_uuid = thread[Metasploit::Framework::Spec::Threads::Suite::UUID_THREAD_LOCAL_VARIABLE] # unmanaged thread, such as the main VM thread unless thread_uuid next end caller = caller_by_thread_uuid[thread_uuid] error_lines << "Thread #{thread_uuid}'s status is #{thread.status.inspect} " \ "and was started here:\n" error_lines.concat(caller) end else error_lines << "Run `rake spec` to log where Thread.new is called." end raise RuntimeError, "#{thread_count} #{'thread'.pluralize(thread_count)} exist(s) when only " \ "#{EXPECTED_THREAD_COUNT_AROUND_SUITE} " \ "#{'thread'.pluralize(EXPECTED_THREAD_COUNT_AROUND_SUITE)} expected after suite runs:\n" \ "#{error_lines.join}" end end end @configured = true end @configured end |
.define_task ⇒ Object
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 |
# File 'lib/metasploit/framework/spec/threads/suite.rb', line 115 def self.define_task Rake::Task.define_task('metasploit:framework:spec:threads:suite') do if Metasploit::Framework::Spec::Threads::Suite::LOG_PATHNAME.exist? Metasploit::Framework::Spec::Threads::Suite::LOG_PATHNAME.delete end parent_pathname = Pathname.new(__FILE__).parent threads_logger_pathname = parent_pathname.join('logger') load_pathname = parent_pathname.parent.parent.parent.parent. # Must append to RUBYOPT or Rubymine debugger will not work ENV['RUBYOPT'] = "#{ENV['RUBYOPT']} -I#{load_pathname} -r#{threads_logger_pathname}" end Rake::Task.define_task(spec: 'metasploit:framework:spec:threads:suite') end |
.each_suite_line {|line| ... } ⇒ Object
Ensure LOG_PATHNAME exists before calling.
Yields each line of LOG_PATHNAME that happened during the suite run.
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 |
# File 'lib/metasploit/framework/spec/threads/suite.rb', line 139 def self.each_suite_line in_suite = false LOG_PATHNAME.each_line do |line| if in_suite if line.start_with?('after(:suite)') break else yield line end else if line.start_with?('before(:suite)') in_suite = true end end end end |
.each_thread_line {|uuid, line| ... } ⇒ Object
Ensure LOG_PATHNAME exists before calling.
Yield each line for each Thread UUID gathered during the suite run.
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 |
# File 'lib/metasploit/framework/spec/threads/suite.rb', line 165 def self.each_thread_line in_thread_caller = false uuid = nil each_suite_line do |line| if in_thread_caller if line.start_with?('END Thread.new caller') in_thread_caller = false next else yield uuid, line end else match = line.match(UUID_REGEXP) if match in_thread_caller = true uuid = match[:uuid] end end end end |
.non_debugger_thread_list ⇒ Object
204 205 206 207 208 209 210 211 |
# File 'lib/metasploit/framework/spec/threads/suite.rb', line 204 def self.non_debugger_thread_list Thread.list.reject { |thread| # don't do `is_a? Debugger::DebugThread` because it requires Debugger::DebugThread to be loaded, which it # won't when not debugging. thread.class.name == 'Debugger::DebugThread' || thread.class.name == 'Debase::DebugThread' } end |