Rails, Minitest and Guard - Why is rb-fsevent taking up over 100% CPU?
Asked Answered
Z

1

7

I'm running guard in my Rails app and the test suite (minutest) has recently stopped working properly.

If I'm lucky, it'll run all of the tests once, maybe twice. After that, it takes so long to respond to even one small test file being changed that using the gem becomes futile.

When following top while the tests run, I can see there's a ruby process that's taking up over 100% of the CPU constantly. Even once all tests are run and I haven't made any changes to the file.

Output from top

The ruby process is:

/Users/Bodacious/.rvm/gems/ruby-2.0.0-p247@MyApp/gems/rb-fsevent-0.9.3/bin/fsevent_watch --latency 0.1 /Users/Bodaiou/Clients/MyApp

(process 31332) in the screenshot attached.

Ruby 2.0.0-p247

Here's my setup:

# Gemfile (with irrelevant gems removed)
gem 'rails', '4.0.0'
group :test do
  gem 'launchy'
  gem "mocha", require: false
  gem 'database_cleaner'
  gem 'selenium-webdriver'
  gem 'capybara-webkit' # for headless javascript tests
  gem 'timecop'
  gem "minitest-focus"
end

group :development, :test do
  gem "minitest-rails"
  gem "minitest-rails-capybara"
  gem 'zeus'
  gem 'guard'
  gem 'guard-minitest'
  gem 'factory_girl_rails'
end


# Guardfile
guard :minitest, all_on_start: false do
  # Rails 4
  watch(%r{^app/(.+)\.rb})                               { |m| "test/#{m[1]}_test.rb" }
  watch(%r{^app/controllers/application_controller\.rb}) { 'test/controllers' }
  watch(%r{^app/controllers/(.+)_controller\.rb})        { |m| "test/integration/#{m[1]}_test.rb" }
  watch(%r{^app/views/(.+)_mailer/.+})                   { |m| "test/mailers/#{m[1]}_mailer_test.rb" }
  watch(%r{^lib/(.+)\.rb})                               { |m| "test/lib/#{m[1]}_test.rb" }
  watch(%r{^test/.+_test\.rb})
  watch(%r{^test/test_helper\.rb})                       { 'test' }

  ignore(%r{^tmp/})

end

# test_helper
ENV["RAILS_ENV"] = "test"
require File.expand_path("../../config/environment", __FILE__)

require 'rails/test_help'
require 'minitest/autorun'
require 'minitest/pride'
require "minitest/rails/capybara"

require "mocha/setup"

# Sidekiq https://github.com/mperham/sidekiq/wiki/Testing
require 'sidekiq/testing'
Sidekiq::Testing.fake!

Dir[Rails.root.join('test', 'support', '*.rb')].each do |file|
  require file
end


class MiniTest::Spec
  include FactoryGirl::Syntax::Methods
  include AllTestHelper

end


class ActiveSupport::TestCase
  include FactoryGirl::Syntax::Methods
  include AllTestHelper
end

class Capybara::Rails::TestCase
  include Rails.application.routes.url_helpers 
  include Capybara::DSL
  include Capybara::Assertions
  include IntegrationTestHelper

  # Called before each Feature spec
  before :each do
  end

  # Called after each Feature spec
  after :each do
    Capybara.reset_sessions!
  end
end

Gem Versions:

  • minitest (4.7.5)
  • minitest-capybara (0.5.0)
  • minitest-focus (1.1.0)
  • minitest-metadata (0.4.0)
  • minitest-rails (0.9.2)
  • minitest-rails-capybara (0.10.0)
  • mobvious-rails (0.1.2)
  • mocha (0.14.0)
  • guard (2.2.4)
  • guard-minitest (2.1.2)
  • rb-fsevent (0.9.3)
Zonnya answered 3/12, 2013 at 18:9 Comment(0)
L
19

SOLUTION:

I resolved it, by adding an 'ignore' statement to my Guardfile. For my rails 3 project it looked like this (./Guardfile):

ignore([%r{^bin/*}, %r{^config/*}, %r{^db/*}, %r{^lib/*}, %r{^log/*}, %r{^public/*}, %r{^tmp/*}])

guard 'rspec', cmd: 'spring rspec', all_after_pass: false, all_on_start: false, focus_on_failed: true do
  # Rails
  watch(%r{^spec/.+_spec\.rb$})
  ...
end

It seems, that this is best practice for guard/rspec/rails.

Guard 'ignore' infos: https://github.com/guard/guard/wiki/Guardfile-DSL---Configuring-Guard#ignore

MY PROBLEM

I was experiencing something quite similar on my mac os x 10.9 machine using:

  • spring (1.0.0)
  • rb-fsevent (0.9.3)
  • growl (1.0.3)
  • rspec-core (2.14.7)
  • rspec-expectations (2.14.4)
  • rspec-mocks (2.14.4)
  • rspec (2.14.1)
  • guard-rspec (4.2.0)
  • listen (2.4.0)
  • rspec-rails (2.14.0)
  • rails (3.2.15)

after starting guard to run my rspec tests, the guard process jumped to 100% load on one core while "idling", it stayed like this long enough for me to qualify as "forever". :)

I tried to run guard

  • with forced polling
  • with no 'watch' statements
  • with spring
  • without spring

No change.

My colleague works on linux on the same project, so he uses rb-inotify instead of rb-fsevent. He's got no load while idling (as you would expect for mac os too...).

As written above, my solution was to add an 'ignore' statement to my Guardfile.

Lambda answered 12/12, 2013 at 12:23 Comment(8)
Thanks - I actually reached the same answer eventually. I was surprised - I thought watch() would implicitly ignore files that weren't included github.com/thibaudgg/rb-fsevent/issues/48Zonnya
I've been trying to solve this for forever! Thank you, this answer is really appreciated! No clue why it'd be watching files you weren't explicitly watching...Como
Greate answer, thanks! I've had to add the vendor folder to the list, as it can also contain a lot of things.Esmerolda
@Eleo weird, .bundle is ignored by default github.com/guard/listen/blob/master/lib/listen/silencer.rbSarong
To me the solution was to ignore public/ which had tons of images.Sarong
@Sarong I stand corrected, then. As often is the case, my assessment was premature. I need to investigate what it was, exactly. Right now I can't reproduce the problem even without the ignore statement, although I was obviously having it since I ended up here. This isn't a Rails project but a Rubygem that I'm working on.Recondite
The regexp could be rewritten as: ruby ignore([%r{^(bin|config|db|lib|log|public|tmp)/*}]) Stealthy
You can also specify just the directories you want to watch, for example: directories %w(app lib test) github.com/guard/guard/wiki/…Prichard

© 2022 - 2024 — McMap. All rights reserved.