Here’s the short version: making your password hashes expensive to compute is great for production environments, but not so much for your tests.
And now the longer version.
Inspired by some recent blog posts, I decided to run perftools.rb against my spec suite to diagnose some slowness.
Low and behold, something really strange appeared at the top of the output:
Finished in 109.78 seconds Total: 12182 samples 3542 29.1% 29.1% 3542 29.1% BCrypt::Engine.__bc_crypt 2262 18.6% 47.6% 2262 18.6% garbage_collector 1590 13.1% 60.7% 2488 20.4% Kernel#require
Hm… 29.1% of CPU time spent inside
BCrypt? Wondering where that might be
coming from, I started digging around and found this:
Devise.setup do |config| config.stretches = 10 config.encryptor = :bcrypt end
Ah! According to the documentation for
bcrypt-ruby a cost factor of 10
stretches into cost factor when using bcrypt) is quite slow.
Well, intentionally slow: “If an attacker was using Ruby to check each
password, they could check ~140,000 passwords a second with MD5 but only ~450
passwords a second with bcrypt().”
Unfortunately, our test suite is the attacker now: most factories depend on a user, and each new user we create has to generate one of these expensive hashes.
So — what would happen if we replace the bcrypt encryptor with our own encryptor class:
# spec/support/devise.rb module Devise module Encryptors class Plain < Base class << self def digest(password, *args) password end def salt(*args) "" end end end end end Devise.encryptor = :plain
And with that in place, let’s try running out suite again:
Finished in 65.72 seconds Total: 8428 samples 2202 26.1% 26.1% 2202 26.1% garbage_collector 1484 17.6% 43.7% 2329 27.6% Kernel#require 684 8.1% 51.9% 684 8.1% IO#write
Success! We managed to save 44 seconds by not encrypting user passwords in
the test environment! Next step? Digging into all that time in