Advertisement
Guest User

Untitled

a guest
Jul 30th, 2015
152
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 6.28 KB | None | 0 0
  1. if ENV['FG_STATS']
  2. at_exit { require 'rubygems'; require 'pry'; binding.pry }
  3. end
  4.  
  5. # Normal test helper code w/ Minitest
  6.  
  7. if ENV['FG_STATS']
  8. class TestInefficiencyAnalyzer
  9. def initialize(adapter)
  10. case adapter
  11. when :minitest
  12. @adapter = MinitestAdapter.new
  13. else
  14. raise 'Unrecognized adapter!'
  15. end
  16. end
  17.  
  18. def start
  19. @adapter.start
  20. end
  21.  
  22. def improvements
  23. @adapter.improvements
  24. end
  25.  
  26. def report
  27. puts
  28. @adapter.improvements.each do |i|
  29. # TODO remove redundancy
  30. puts "In #{i[:test]}:"
  31. i[:improvements].each do |im|
  32. puts "\tAt #{im[:line]}: #{im[:from]} => #{im[:to]}"
  33. end
  34. puts
  35. end
  36. nil
  37. end
  38.  
  39. class MinitestAdapter
  40. MinitestSpy = Module.new
  41. MinitestSpy.extend(Spy::API)
  42.  
  43. attr_accessor :improvements
  44.  
  45. def initialize
  46. @improvements = []
  47. end
  48.  
  49. def start
  50. adapter = self
  51. MinitestSpy.on(Minitest, :run_one_method)
  52. .wrap do |r, test, method_name, *args, &block|
  53. improvement = ImprovementFinder.find_improvements(adapter, r.name, test, block)
  54. adapter.improvements << improvement if improvement
  55. end
  56. end
  57.  
  58. # Returns boolean whether test passed
  59. def test_passes?(block)
  60. result = block.call
  61. result.passed?
  62. end
  63. end
  64.  
  65. class ImprovementRun
  66. RunSpy = Module.new
  67. RunSpy.extend(Spy::API)
  68.  
  69. attr_reader :inefficient, :efficient, :nth_call
  70. attr_accessor :in_call, :caller
  71.  
  72. def initialize(adapter, test_block, inefficient, efficient, nth_call)
  73. @adapter = adapter
  74. @test_block = test_block
  75. @inefficient = inefficient
  76. @efficient = efficient
  77. @nth_call = nth_call
  78. @in_call = false
  79. end
  80.  
  81. def run
  82. manage_call_recursion
  83. stub
  84. run_test
  85. ensure
  86. clean_up
  87. end
  88.  
  89. private
  90.  
  91. def manage_call_recursion
  92. run = self
  93. # Deal with #build calling create or #create calling build as well
  94. # as recursion. Only work with top-level calls
  95. [:create, :build].each do |sym|
  96. RunSpy.on(FactoryGirl, sym).wrap do |*args, &block|
  97. if run.in_call == false
  98. run.in_call = true
  99. block.call
  100. run.in_call = false
  101. end
  102. end
  103. end
  104. end
  105.  
  106. def stub
  107. run = self
  108. call_count = 0
  109. FactoryGirl.define_singleton_method inefficient.name, -> (*args) {
  110. # recursionnnnn
  111. if run.in_call
  112. return inefficient.call(*args)
  113. end
  114.  
  115. call_count += 1
  116. if call_count == run.nth_call
  117. # TODO: fix caller when recursive or composite call
  118. run.caller = caller[0]
  119. res = run.efficient.call(*args)
  120. else
  121. res = run.inefficient.call(*args)
  122. end
  123. }
  124. end
  125.  
  126. def run_test
  127. if @adapter.test_passes?(@test_block)
  128. {
  129. line: self.caller[/\d+/],
  130. from: inefficient.name,
  131. to: efficient.name,
  132. #source: self.caller.sub(Rails.root.to_s, ''),
  133. #index: nth_call
  134. }
  135. end
  136. end
  137.  
  138. def clean_up
  139. FactoryGirl.define_singleton_method inefficient.name, inefficient
  140. RunSpy.restore(:all)
  141. end
  142. end
  143.  
  144. class ImprovementFinder
  145. FgSpy = Module.new
  146. FgSpy.extend(Spy::API)
  147.  
  148. def self.find_improvements(adapter, test, method_name, block)
  149. new(adapter, test, method_name, block).find_improvements
  150. end
  151.  
  152. # Find improvements in the given test, method, block
  153. def initialize(adapter, test, method_name, block)
  154. @adapter = adapter
  155. @test = test
  156. @method_name = method_name
  157. @block = block
  158. @improvements = []
  159.  
  160. # Save original method bindings
  161. @fg_create = FactoryGirl.method(:create)
  162. @fg_build = FactoryGirl.method(:build)
  163. @fg_build_stubbed = FactoryGirl.method(:build_stubbed)
  164. end
  165.  
  166. def find_improvements
  167. dry_run
  168. find_improvements_create
  169. find_improvements_build
  170. report
  171. end
  172.  
  173. private
  174.  
  175. # Perform a dry run of the test to figure out how many times
  176. # we call each inefficient method
  177. def dry_run
  178. in_spy = false # Don't count recursive/nested calls
  179. create_spy = FgSpy.on(FactoryGirl, :create)
  180. build_spy = FgSpy.on(FactoryGirl, :build)
  181. [create_spy, build_spy].each do |s|
  182. s.when { !in_spy }
  183. s.before { in_spy = true }
  184. s.after { in_spy = false }
  185. end
  186. @block.call
  187. @dry_calls_create = create_spy.call_count
  188. @dry_calls_build = build_spy.call_count
  189. FgSpy.restore(:all)
  190. end
  191.  
  192. # Try substituting each call to the inefficient method with
  193. # the more efficient one. See if the tests still pass with
  194. # the substitution
  195. def find_improvements_create
  196. (1..@dry_calls_create).to_a.each do |idx|
  197. has_passed = try_create_to_build_stubbed(idx)
  198. # #build_stubbed is better than #build; use it if we can
  199. try_create_to_build(idx) if !has_passed
  200. end
  201. end
  202.  
  203. def find_improvements_build
  204. (1..@dry_calls_build).to_a.each do |idx|
  205. try_build_to_build_stubbed(idx)
  206. end
  207. end
  208.  
  209. def try_create_to_build(idx)
  210. find_improvement(@fg_create, @fg_build, idx)
  211. end
  212.  
  213. def try_create_to_build_stubbed(idx)
  214. find_improvement(@fg_create, @fg_build_stubbed, idx)
  215. end
  216.  
  217. def try_build_to_build_stubbed(idx)
  218. find_improvement(@fg_build, @fg_build_stubbed, idx)
  219. end
  220.  
  221. def report
  222. if @improvements.any?
  223. {
  224. test: @test,
  225. method_name: @method_name,
  226. improvements: @improvements
  227. }
  228. end
  229. end
  230.  
  231. # Stub out the nth call of the inefficient method with the
  232. # efficient one. See if the tests still pass
  233. def find_improvement(inefficient, efficient, nth_call)
  234. improvement = ImprovementRun.new(@adapter, @block, inefficient, efficient, nth_call).run
  235. @improvements << improvement if improvement
  236. !!improvement
  237. end
  238. end
  239. end
  240.  
  241. analyzer = TestInefficiencyAnalyzer.new(:minitest)
  242. analyzer.start
  243. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement