lib/chefspec/cacher.rb
module ChefSpec # # The cacher module allows for ultra-fast tests by caching the results of a # CCR in memory across an example group. In testing, this can reduce the # total testing time by a factor of 10x. This strategy is _not_ the default # behavior, because it has implications surrounding stubbing and is _not_ # threadsafe! # # The credit for this approach and code belongs to Juri Timošin (DracoAter). # Please see his original blog post below for an in-depth explanation of how # and why this approach is faster. # # @example Using the Cacher module # First, require the Cacher module in your +spec_helper.rb+: # # RSpec.configure do |config| # config.extend(ChefSpec::Cacher) # end # # Next, change your +let+ blocks to +cached+ blocks: # # let(:chef_run) { ... } #=> cached(:chef_run) { ... } # # Finally, celebrate! # # @warn # This strategy is only recommended for advanced users, as it makes # stubbing slightly more difficult and indirect! # # @see http://dracoater.blogspot.com/2013/12/testing-chef-cookbooks-part-25-speeding.html # module Cacher @@cache = {} FINALIZER = lambda { |id| @@cache.delete(id) } def cached(name, &block) location = ancestors.first.metadata[:location] unless location.nil? location += ancestors.first.metadata[:description] unless ancestors.first.metadata[:description].nil? location += ancestors.first.metadata[:scoped_id] unless ancestors.first.metadata[:scoped_id].nil? end location ||= ancestors.first.metadata[:parent_example_group][:location] define_method(name) do key = [location, name.to_s].join(".") unless @@cache.key?(Thread.current.object_id) ObjectSpace.define_finalizer(Thread.current, FINALIZER) end @@cache[Thread.current.object_id] ||= {} @@cache[Thread.current.object_id][key] ||= instance_eval(&block) end end def cached!(name, &block) cached(name, &block) before { send(name) } end end end RSpec.configure do |config| config.extend(ChefSpec::Cacher) end