Testing Rails Plugins on All Cores with parallel_tests

About 90% of my development is done on Redmine and well over half of that is done on Redmine plugins. That means that I write tests; a lot of tests. Over time though, they get slower and slower and start to become a drain on productivity (“5 minutes to run the tests on a feature that took 30 seconds?!?!”).

The limiting factor is that Rails runs tests sequentially, so it only takes advantage of a single CPU core. I’ve started to use a great tool called parallel_tests to use all four cores but it hasn’t been working on tests for Rails plugins’ due to how it loads.

Until now…

With a bit of hacking, I’ve figured out how to load parallel_tests inside my plugin’s test suite. The setup was actually really simple:

  1. Setup parallel_tests just like in the Readme, but stop at step 4.
  2. Load the parallel_test rake file into your plugin (below)
  3. Run the tests

Loading parallel_tests

Just pop this into your plugin’s Rakefile (assuming you installed the plugin version of parallel_tests)

1
2
3
4
5
parallel_tests = (File.join(File.dirname(__FILE__), '..', 'parallel_tests','lib','tasks','parallel_tests.rake'))
if File.exists? parallel_tests
  RAILS_ROOT = File.dirname(__FILE__)
  import parallel_tests
end

Using rake’s import statement, the parallel_tests.rake file gets loaded directly into the plugin’s Rakefile. This makes the rake parallel tasks available.

I also had to define RAILS_ROOT because I monkey around with it in my plugin. You may be able to remove it in your plugin.

Results

I set this up for my redmine_kanban plugin and it cut my test suite’s running time from 4 minutes down to 2 minutes 38 seconds, a savings of 1 minute 22 seconds. That’s a massive improvement when you start running the test suite 10, 20, or 50 times a day.

$ time rake
(in /home/edavis/dev/redmine/redmine-core/vendor/plugins/redmine_kanban)
/usr/bin/ruby1.8 -I"lib:test" "/usr/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake/rake_test_loader.rb" "test/unit/kanban_issue_test.rb" "test/unit/helpers/kanbans_helper_test.rb" "test/unit/issue_test.rb" "test/unit/sanity_test.rb" "test/unit/kanban_test.rb"
  * DEFERRED: #update_issue_attributes should return false if the issue didn't save.
Loaded suite /usr/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake/rake_test_loader
Started
...................................................................................
Finished in 63.503572 seconds.

83 tests, 267 assertions, 0 failures, 0 errors
/usr/bin/ruby1.8 -I"lib:test" "/usr/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake/rake_test_loader.rb" "test/functional/kanbans_controller_test.rb"
Loaded suite /usr/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake/rake_test_loader
Started
................................
Finished in 139.429962 seconds.

32 tests, 79 assertions, 0 failures, 0 errors
/usr/bin/ruby1.8 -I"lib:test" "/usr/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake/rake_test_loader.rb" "test/integration/kanban_board_test.rb"
Loaded suite /usr/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake/rake_test_loader
Started
..
Finished in 9.012205 seconds.

2 tests, 12 assertions, 0 failures, 0 errors

real    3m59.730s
user    3m32.413s
sys     0m21.733s
$ time rake parallel:test
(in /home/edavis/dev/redmine/redmine-core/vendor/plugins/redmine_kanban)
/home/edavis/dev/redmine/redmine-core/vendor/plugins/parallel_tests/lib/tasks/../parallel_tests.rb:4: warning: already initialized constant VERSION
4 processes for 7 tests, ~ 1 tests per process
Loaded suite -e
Started
.  * DEFERRED: #update_issue_attributes should return false if the issue didn't save.
Loaded suite -e
Started
.Loaded suite -e
Started
.Loaded suite -e
Started
.............................................
Finished in 14.261201 seconds.

31 tests, 56 assertions, 0 failures, 0 errors
...
Finished in 14.744914 seconds.

14 tests, 43 assertions, 0 failures, 0 errors
........................................
Finished in 52.973448 seconds.

41 tests, 180 assertions, 0 failures, 0 errors
...........................
Finished in 147.211034 seconds.

32 tests, 79 assertions, 0 failures, 0 errors

Results:
41 tests, 180 assertions, 0 failures, 0 errors
14 tests, 43 assertions, 0 failures, 0 errors
31 tests, 56 assertions, 0 failures, 0 errors
32 tests, 79 assertions, 0 failures, 0 errors

Took 156.279382 seconds

real    2m38.223s
user    0m1.600s
sys     0m0.416s

Don’t settle for Rails only using one core, put your quad core behemoth back to work.

Eric Davis