Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- if ENV['FG_STATS']
- at_exit { require 'rubygems'; require 'pry'; binding.pry }
- end
- # Normal test helper code w/ Minitest
- if ENV['FG_STATS']
- class TestInefficiencyAnalyzer
- def initialize(adapter)
- case adapter
- when :minitest
- @adapter = MinitestAdapter.new
- else
- raise 'Unrecognized adapter!'
- end
- end
- def start
- @adapter.start
- end
- def improvements
- @adapter.improvements
- end
- def report
- puts
- @adapter.improvements.each do |i|
- # TODO remove redundancy
- puts "In #{i[:test]}:"
- i[:improvements].each do |im|
- puts "\tAt #{im[:line]}: #{im[:from]} => #{im[:to]}"
- end
- puts
- end
- nil
- end
- class MinitestAdapter
- MinitestSpy = Module.new
- MinitestSpy.extend(Spy::API)
- attr_accessor :improvements
- def initialize
- @improvements = []
- end
- def start
- adapter = self
- MinitestSpy.on(Minitest, :run_one_method)
- .wrap do |r, test, method_name, *args, &block|
- improvement = ImprovementFinder.find_improvements(adapter, r.name, test, block)
- adapter.improvements << improvement if improvement
- end
- end
- # Returns boolean whether test passed
- def test_passes?(block)
- result = block.call
- result.passed?
- end
- end
- class ImprovementRun
- RunSpy = Module.new
- RunSpy.extend(Spy::API)
- attr_reader :inefficient, :efficient, :nth_call
- attr_accessor :in_call, :caller
- def initialize(adapter, test_block, inefficient, efficient, nth_call)
- @adapter = adapter
- @test_block = test_block
- @inefficient = inefficient
- @efficient = efficient
- @nth_call = nth_call
- @in_call = false
- end
- def run
- manage_call_recursion
- stub
- run_test
- ensure
- clean_up
- end
- private
- def manage_call_recursion
- run = self
- # Deal with #build calling create or #create calling build as well
- # as recursion. Only work with top-level calls
- [:create, :build].each do |sym|
- RunSpy.on(FactoryGirl, sym).wrap do |*args, &block|
- if run.in_call == false
- run.in_call = true
- block.call
- run.in_call = false
- end
- end
- end
- end
- def stub
- run = self
- call_count = 0
- FactoryGirl.define_singleton_method inefficient.name, -> (*args) {
- # recursionnnnn
- if run.in_call
- return inefficient.call(*args)
- end
- call_count += 1
- if call_count == run.nth_call
- # TODO: fix caller when recursive or composite call
- run.caller = caller[0]
- res = run.efficient.call(*args)
- else
- res = run.inefficient.call(*args)
- end
- }
- end
- def run_test
- if @adapter.test_passes?(@test_block)
- {
- line: self.caller[/\d+/],
- from: inefficient.name,
- to: efficient.name,
- #source: self.caller.sub(Rails.root.to_s, ''),
- #index: nth_call
- }
- end
- end
- def clean_up
- FactoryGirl.define_singleton_method inefficient.name, inefficient
- RunSpy.restore(:all)
- end
- end
- class ImprovementFinder
- FgSpy = Module.new
- FgSpy.extend(Spy::API)
- def self.find_improvements(adapter, test, method_name, block)
- new(adapter, test, method_name, block).find_improvements
- end
- # Find improvements in the given test, method, block
- def initialize(adapter, test, method_name, block)
- @adapter = adapter
- @test = test
- @method_name = method_name
- @block = block
- @improvements = []
- # Save original method bindings
- @fg_create = FactoryGirl.method(:create)
- @fg_build = FactoryGirl.method(:build)
- @fg_build_stubbed = FactoryGirl.method(:build_stubbed)
- end
- def find_improvements
- dry_run
- find_improvements_create
- find_improvements_build
- report
- end
- private
- # Perform a dry run of the test to figure out how many times
- # we call each inefficient method
- def dry_run
- in_spy = false # Don't count recursive/nested calls
- create_spy = FgSpy.on(FactoryGirl, :create)
- build_spy = FgSpy.on(FactoryGirl, :build)
- [create_spy, build_spy].each do |s|
- s.when { !in_spy }
- s.before { in_spy = true }
- s.after { in_spy = false }
- end
- @block.call
- @dry_calls_create = create_spy.call_count
- @dry_calls_build = build_spy.call_count
- FgSpy.restore(:all)
- end
- # Try substituting each call to the inefficient method with
- # the more efficient one. See if the tests still pass with
- # the substitution
- def find_improvements_create
- (1..@dry_calls_create).to_a.each do |idx|
- has_passed = try_create_to_build_stubbed(idx)
- # #build_stubbed is better than #build; use it if we can
- try_create_to_build(idx) if !has_passed
- end
- end
- def find_improvements_build
- (1..@dry_calls_build).to_a.each do |idx|
- try_build_to_build_stubbed(idx)
- end
- end
- def try_create_to_build(idx)
- find_improvement(@fg_create, @fg_build, idx)
- end
- def try_create_to_build_stubbed(idx)
- find_improvement(@fg_create, @fg_build_stubbed, idx)
- end
- def try_build_to_build_stubbed(idx)
- find_improvement(@fg_build, @fg_build_stubbed, idx)
- end
- def report
- if @improvements.any?
- {
- test: @test,
- method_name: @method_name,
- improvements: @improvements
- }
- end
- end
- # Stub out the nth call of the inefficient method with the
- # efficient one. See if the tests still pass
- def find_improvement(inefficient, efficient, nth_call)
- improvement = ImprovementRun.new(@adapter, @block, inefficient, efficient, nth_call).run
- @improvements << improvement if improvement
- !!improvement
- end
- end
- end
- analyzer = TestInefficiencyAnalyzer.new(:minitest)
- analyzer.start
- end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement