8

I'm trying to speed up a large RSpec project's tests. In addition to using RSpec's --profile option I wanted to get the longest running test files [1] printed out.

In my spec_helper.rb I dump the classes being tested and total time to a file, however as we have spec/model and spec/request directories I'd really like to be able to print the current test's filename and not just the class name (described_class), so that the user can disambiguate between model/foo_spec.rb and request/foo_spec.rb when optimizing.

In a before block in the spec/spec_helper.rb, how can I get the current test file's filename?

My (heavily trimmed) spec_helper looks like this:

config.before :all do
  @start_time = Time.now
end

config.after :all do |test|
  timings.push({ :name => test.described_class,
                 :file => 'I do not know how to get this',
                 :duration_in_seconds => (Time.now - @start_time) })
end

config.after :suite do
  timing_logfile_name = 'log/rspec_file_times.log'
  timing_logfile = "#{File.dirname(__FILE__)}/../#{timing_logfile_name}"
  file = File.open(timing_logfile, 'w')
  timings.sort_by{ |timing| timing[:duration_in_seconds].to_f }.reverse!.each do |timing|
    file.write( sprintf("%-25.25s    % 9.3f seconds\n",
                        timing[:name], timing[:duration_in_seconds]) )
  end
  file.close
  tell_if_verbose("Overall test times are logged in '#{timing_logfile_name}'")
end

This doesn't seem to be available in the curretn RSpec meta-data, but I'm hoping someone more familiar with the internals can think of a way to expose it. Thanks, Dave

[1] Often a file with, say, 100 examples in it yields more speed up than a single example from --profile - when that large file's before :each / before :all blocks are targetted, obviously even a ms saved is multiplied up by the number of tests in the file. Using this technique in addition to --profile helped me a lot.

David Kennedy
  • 333
  • 1
  • 3
  • 13

1 Answers1

13

As long as you're just using this for profiling your tests to identify which files need to be improved, you should be able to toss this into your spec_helper.rb file (and remove it afterwards). I fully understand that this is not pretty/clean/elegant/acceptible in production environments and I disavow that I ever wrote it :)

config.before(:each) do |example|
  path = example.metadata[:example_group][:file_path]
  curr_path = config.instance_variable_get(:@curr_file_path)
  if (curr_path.nil? || path != curr_path)
    config.instance_variable_set(:@curr_file_path, path)
    puts path
  end
end
Niels Kristian
  • 8,661
  • 11
  • 59
  • 117
plainjimbo
  • 7,070
  • 9
  • 41
  • 55
  • Thanks; this doesn't directly sort out my current issue as I only seem to have the example metadata in a before :each, and not in the :all. However, I can rewrite until I get close enough to what I want. – David Kennedy Mar 29 '12 at 11:50
  • yeah, before :all doesn't get metadata, but what this is essentially doing is whatching each test/example and printing out whenever the source file changes. If you want to dig a little deeper, place a "pp config" call in config.before(:all) then exec one test to dump everything that is in the config object. You might find something useful in there that I missed. – plainjimbo Mar 29 '12 at 17:47
  • 2
    In order to get access to `example`, I had to change `config.before(:each) do` to `config.before(:each) do |example|` (i.e. I needed to add `|example|`). I assume that this is due to some change in RSpec since this answer was originally written. (I'm currently using RSpec version 3.7.0.) – David Runger May 19 '18 at 00:25