module_test.rb 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. require 'rex/stopwatch'
  2. module Msf
  3. module ModuleTest
  4. attr_accessor :tests
  5. attr_accessor :failures
  6. attr_accessor :skipped
  7. class SkipTestError < ::Exception
  8. end
  9. def initialize(info = {})
  10. @tests = 0
  11. @failures = 0
  12. @skipped = 0
  13. super
  14. register_options(
  15. [
  16. OptString.new("TestName", [false, "Run a specified test method name.", nil]),
  17. ], self.class
  18. )
  19. end
  20. def run_all_tests
  21. tests = datastore['TestName'].present? ? [datastore['TestName'].to_sym] : self.methods.select { |m| m.to_s =~ /^test_/ }
  22. tests.each { |test_method|
  23. begin
  24. unless respond_to?(test_method)
  25. print_error("test method #{test_method} not found")
  26. next
  27. end
  28. self.send(test_method)
  29. rescue SkipTestError => e
  30. # If the entire def is skipped, increment tests and skip count
  31. @tests += 1
  32. @skipped += 1
  33. print_status("SKIPPED: def #{test_method} (#{e.message})")
  34. end
  35. }
  36. end
  37. def skip(msg = "No reason given")
  38. raise SkipTestError, msg
  39. end
  40. def it(msg = "", &block)
  41. @current_it_msg = msg
  42. @tests += 1
  43. begin
  44. result = block.call
  45. unless result
  46. @failures += 1
  47. print_error("FAILED: #{error}") if error
  48. @current_it_msg = nil
  49. print_error("FAILED: #{msg}")
  50. return
  51. end
  52. rescue SkipTestError => e
  53. @skipped += 1
  54. @current_it_msg = nil
  55. print_status("SKIPPED: #{msg} (#{e.message})")
  56. return
  57. rescue ::Exception => e
  58. @failures += 1
  59. print_error("FAILED: #{msg}")
  60. print_error("Exception: #{e.class}: #{e}")
  61. dlog("Exception in testing - #{msg}")
  62. dlog("Call stack: #{e.backtrace.join("\n")}")
  63. return
  64. ensure
  65. @current_it_msg = nil
  66. end
  67. print_good("#{msg}")
  68. end
  69. def pending(msg = "", &block)
  70. print_status("PENDING: #{msg}")
  71. end
  72. # @return [Integer] The number of tests that have passed
  73. def passed
  74. @tests - @failures
  75. end
  76. # When printing to console, additionally prepend the current test name
  77. [
  78. :print,
  79. :print_line,
  80. :print_status,
  81. :print_good,
  82. :print_warning,
  83. :print_error,
  84. :print_bad,
  85. ].each do |method|
  86. define_method(method) do |msg|
  87. super(@current_it_msg ? "[#{@current_it_msg}] #{msg}" : msg)
  88. end
  89. end
  90. end
  91. module ModuleTest::PostTest
  92. include ModuleTest
  93. def run
  94. print_status("Running against session #{datastore["SESSION"]}")
  95. print_status("Session type is #{session.type} and platform is #{session.platform}")
  96. @tests = 0
  97. @failures = 0
  98. @skipped = 0
  99. _res, elapsed_time = Rex::Stopwatch.elapsed_time do
  100. run_all_tests
  101. end
  102. vprint_status("Testing complete in #{elapsed_time.round(2)} seconds")
  103. status = "Passed: #{passed}; Failed: #{@failures}; Skipped: #{@skipped}"
  104. if @failures > 0
  105. print_error(status)
  106. else
  107. print_status(status)
  108. end
  109. end
  110. end
  111. module ModuleTest::PostTestFileSystem
  112. def initialize(info = {})
  113. super
  114. register_options(
  115. [
  116. OptBool.new("AddEntropy", [false, "Add entropy token to file and directory names.", true]),
  117. OptString.new('BaseDirectoryName', [true, 'Directory name to create', 'meterpreter-test-dir']),
  118. OptString.new("BaseFileName", [true, "File/dir base name", "meterpreter-test"]),
  119. ], self.class
  120. )
  121. @directory_stack = []
  122. end
  123. def push_test_directory
  124. @directory_stack.push(_file_system.pwd)
  125. # Find the temp directory
  126. tmp = _file_system.get_env("TMP") || _file_system.get_env("TMPDIR")
  127. # mettle fallback
  128. tmp = '/tmp' if tmp.nil? && _file_system.directory?('/tmp')
  129. raise "Could not find tmp directory" if tmp == nil || !_file_system.directory?(tmp)
  130. vprint_status("Setup: changing working directory to tmp: #{tmp}")
  131. _file_system.cd(tmp)
  132. vprint_status("Setup: Creating clean directory")
  133. if datastore["AddEntropy"]
  134. entropy_value = '-' + ('a'..'z').to_a.shuffle[0, 8].join
  135. else
  136. entropy_value = ""
  137. end
  138. clean_test_directory = datastore['BaseDirectoryName'] + entropy_value
  139. _file_system.mkdir(clean_test_directory)
  140. _file_system.cd(clean_test_directory)
  141. vprint_status("Setup: Now in #{_file_system.pwd}")
  142. end
  143. def pop_test_directory
  144. previous_directory = @directory_stack.pop
  145. unless previous_directory.nil?
  146. vprint_status("Cleanup: changing working directory back to #{previous_directory}")
  147. _file_system.cd(previous_directory)
  148. end
  149. end
  150. # Private PostFile wrapper to ensure we don't clobber the test module's namespace with the Msf::Post::File mixin methods
  151. class FileSystem
  152. include Msf::Post::File
  153. def initialize(mod)
  154. @mod = mod
  155. @session = mod.session
  156. end
  157. private
  158. def vprint_status(s)
  159. @mod.vprint_status(s)
  160. end
  161. def register_dir_for_cleanup(path)
  162. end
  163. attr_reader :session
  164. end
  165. def _file_system
  166. FileSystem.new(self)
  167. end
  168. end
  169. end