Created
March 27, 2017 15:42
-
-
Save palkan/4088ab57191f6b537ef3e4d75939a190 to your computer and use it in GitHub Desktop.
FactoryDoc: detect useless data generation in tests
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module FactoryGirl | |
module Doctor | |
module FloatDuration | |
refine Float do | |
def duration | |
t = self | |
format("%02d:%02d.%03d", t / 60, t % 60, t.modulo(1) * 1000) | |
end | |
end | |
end | |
using FloatDuration | |
module FactoryExt | |
def run(strategy = @strategy) | |
Doctor.within_factory(strategy) { super } | |
end | |
end | |
class << self | |
attr_reader :count, :time | |
def init | |
@depth = 0 | |
reset | |
FactoryGirl::FactoryRunner.prepend FactoryExt | |
end | |
def within_factory(strategy) | |
return yield if ignore? | |
ts = Time.now if @depth.zero? | |
@depth += 1 | |
@count += 1 if strategy == :create | |
yield | |
ensure | |
@depth -= 1 | |
if @depth.zero? | |
delta = (Time.now - ts) | |
@time += delta | |
end | |
end | |
def ignore | |
@ignored = true | |
res = yield | |
@ignored = false | |
res | |
end | |
def reset | |
@count = 0 | |
@time = 0.0 | |
end | |
def within_factory? | |
@depth.positive? | |
end | |
def ignore? | |
@ignored == true | |
end | |
end | |
class Profiler | |
IGNORED_QUERIES_PATTERN = %r{( | |
pg_table| | |
pg_attribute| | |
pg_namespace| | |
show\stables| | |
pragma| | |
sqlite_master/rollback| | |
\ATRUNCATE TABLE| | |
\AALTER TABLE| | |
\ABEGIN| | |
\ACOMMIT| | |
\AROLLBACK| | |
\ARELEASE| | |
\ASAVEPOINT | |
)}xi | |
NOTIFICATIONS = [:example_started, :example_finished].freeze | |
def initialize | |
@queries = [] | |
@example_groups = Hash.new { |h, k| h[k] = [] } | |
ActiveSupport::Notifications.subscribe('sql.active_record') do |_name, _start, _finish, _id, query| | |
next if Doctor.within_factory? | |
next if query[:sql] =~ IGNORED_QUERIES_PATTERN | |
@queries << query[:sql] | |
end | |
end | |
def example_started(_notification) | |
@queries.clear | |
Doctor.reset | |
end | |
def example_finished(notification) | |
return if notification.example.pending? | |
if Doctor.count.positive? && @queries.size.zero? | |
group = notification.example.example_group.parent_groups.last | |
notification.example.metadata.merge!( | |
factories: Doctor.count, | |
time: Doctor.time | |
) | |
@example_groups[group] << notification.example | |
end | |
end | |
def print | |
return if @example_groups.empty? | |
output.puts( | |
"\n\nFactoryDoctor found useless data generation "\ | |
"in the following examples\n" | |
) | |
total_time = 0.0 | |
@example_groups.each do |group, examples| | |
out = ["#{group.description} (#{group.metadata[:location]})" | |
examples.each do |ex| | |
total_time += ex.metadata[:time] | |
out << " #{ex.description} (#{ex.metadata[:location]}) – #{ex.metadata[:factories]} created objects, #{ex.metadata[:time].duration}" | |
end | |
output.puts out.join("\n") | |
end | |
output.puts "\nTotal wasted time: #{total_time.duration}\n" | |
end | |
private | |
def output | |
RSpec.configuration.output_stream | |
end | |
end | |
end | |
end | |
if ENV['FDOC'] | |
FactoryGirl::Doctor.init | |
RSpec.configure do |config| | |
listener = FactoryGirl::Doctor::Profiler.new | |
config.reporter.register_listener(listener, *FactoryGirl::Doctor::Profiler::NOTIFICATIONS) | |
config.after(:suite) { listener.print } | |
end | |
end |
use FDOC=flamegraph rspec
if you want a flamegraph generated
Look likes missing an '|' on https://gist.github.com/palkan/4088ab57191f6b537ef3e4d75939a190#file-factory_doctor-rb-L79
\ASAVEPOINT|
I've cloned the gist and I'd corrected some errors
https://gist.github.com/AntonErmolenko/d927eec32d1c1382c0ef559af30f602c
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
require 'support/factory_doctor'
intorails_helper.rb
FDOC=1 rspec