ecomdev-chefspec 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 01543e12806b48f668ac7ad1fd5ed983ad3d0277
4
+ data.tar.gz: 07d25f11fc0966b44312207590c7aa81c5ec1b4c
5
+ SHA512:
6
+ metadata.gz: ca516b00f234b7120a53974fd1d218fe5a82387c1e37b271c88b3cd8cc7f0e4f29012577b8c3136e2e53417e5694b7657c289905ba1889537cd19b0cd3b5a359
7
+ data.tar.gz: 11059f3e473023a9c6ef027e0d05560426954b8cb3bb69434f920d70c628f4e58f4ad9c09022a31058e3238d8562e81df8e7ea226d75c0934970afd0f1480b77
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ rvm:
2
+ - 1.9.3
3
+ - 2.0.0
4
+ - 2.1
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in ecomdev-chefspec.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Ivan Chepurnyi
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,79 @@
1
+ # EcomDev::ChefSpec
2
+
3
+ A set of valuable helper to make writing ChefSpecs even more easier.
4
+
5
+ Features:
6
+
7
+ * You can define matchers for your LWRP and HWRP by using DSL based file in your specs directory.
8
+ * By default it looks in `spec/matchers.rb`, `spec/matchers/*.rb`, `spec/library/matchers.rb`, `spec/library/matchers/*.rb`
9
+ * Example of the file DSL
10
+
11
+ ```ruby
12
+ matcher :resource_name, :action
13
+ matcher :resource_name, [:delete, :create]
14
+ runner :resource_name
15
+ ```
16
+
17
+ * It automatically includes matchers from other recipes, that contain such a Ruby file
18
+
19
+ * By default it mocks any inclusion of recipes, except described one. You can specify additional one by calling `allow_recipe` in spec DSL.
20
+ * Allows to supply additional cookbook directory for your specs. Just add in `spec_helper.rb` file the following code
21
+
22
+ ```ruby
23
+ EcomDev::ChefSpec::Configuration.cookbook_path('path/to/your/test/cookbooks')
24
+ EcomDev::ChefSpec::Configuration.cookbook_path('another/path/to/your/test/cookbooks')
25
+ ```
26
+
27
+ It will automatically include the following paths for cookbook search:
28
+
29
+ * Berkshelf or Librarian path if any of those was included before
30
+ * `path/to/your/test/cookbooks`
31
+ * `another/path/to/your/test/cookbooks`
32
+
33
+
34
+ ## Build Status
35
+
36
+ [![Develop Branch](https://api.travis-ci.org/IvanChepurnyi/ecomdev-chefspec.svg?branch=develop)](https://travis-ci.org/IvanChepurnyi/ecomdev-chefspec) **Next Release Branch**
37
+
38
+ [![Master Branch](https://api.travis-ci.org/IvanChepurnyi/ecomdev-chefspec.svg)](https://travis-ci.org/IvanChepurnyi/ecomdev-chefspec) **Current Stable Release**
39
+
40
+
41
+ ## Installation
42
+
43
+ Add this line to your application's Gemfile:
44
+
45
+ ```ruby
46
+ gem 'ecomdev-chefspec'
47
+ ```
48
+
49
+ And then execute:
50
+
51
+ ```bash
52
+ bundle
53
+ ```
54
+
55
+ Or install it yourself as:
56
+
57
+ ```bash
58
+ gem install ecomdev-chefspec
59
+ ```
60
+
61
+ ## Usage
62
+
63
+ Inside of your specs helper you should include this library after chefspec
64
+
65
+ ```ruby
66
+ require 'ecomdev/chefspec'
67
+ ```
68
+
69
+ Please note, that you should include it in the end, after all the chef spec files are included. E.g. if you use bershelf loader for your cookbooks, you should include `ecomdev/chefspec` after you've included `chefspec/berkshelf`
70
+
71
+
72
+
73
+ ## Contributing
74
+
75
+ 1. Fork it ( https://github.com/IvanChepurnyi/ecomdev-chefspec/fork )
76
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
77
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
78
+ 4. Push to the branch (`git push origin my-new-feature`)
79
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:unit) do |t|
5
+ t.rspec_opts = [].tap do |a|
6
+ a.push('--color')
7
+ a.push('--format progress')
8
+ end.join(' ')
9
+ end
10
+
11
+ desc 'Run all tests'
12
+ task :test => [:unit]
13
+
14
+ task :default => [:test]
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'ecomdev/chefspec/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'ecomdev-chefspec'
8
+ spec.version = EcomDev::ChefSpec::VERSION
9
+ spec.authors = ['Ivan Chepurnyi']
10
+ spec.email = ['ivan.chepurnyi@ecomdev.org']
11
+ spec.summary = 'A collection of helpers for chef spec, to make easier writing recipe specs'
12
+ spec.description = spec.summary
13
+ spec.homepage = 'https://github.com/IvanChepurnyi/ecomdev-chefspec'
14
+ spec.license = 'GPLv3'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency 'chefspec', '~> 4.0'
22
+ spec.add_development_dependency "bundler", "~> 1.6"
23
+ spec.add_development_dependency "rake"
24
+ end
@@ -0,0 +1,2 @@
1
+ # Loads chefspec definition file
2
+ require_relative 'ecomdev/chefspec'
@@ -0,0 +1,9 @@
1
+ require 'chefspec' unless defined?(ChefSpec) # Require chef spec only if it is not required before
2
+
3
+ require_relative 'chefspec/version'
4
+ require_relative 'chefspec/configuration'
5
+ require_relative 'chefspec/resource/matcher'
6
+ require_relative 'chefspec/api'
7
+
8
+ # Registers Resource Matchers callbacks for RSpec
9
+ EcomDev::ChefSpec::Resource::Matcher.register
@@ -0,0 +1,3 @@
1
+ require 'chefspec/api'
2
+
3
+ require_relative 'api/include_recipe'
@@ -0,0 +1,13 @@
1
+ require_relative '../stub/include_recipe'
2
+
3
+ module ChefSpec::API
4
+ module EcomDevIncludeRecipe
5
+ def allow_recipe(*recipe_name)
6
+ recipe_name.flatten.each do |recipe|
7
+ EcomDev::ChefSpec::Stub::IncludeRecipe.allow_recipe(recipe)
8
+ end
9
+ end
10
+ end
11
+ end
12
+
13
+ EcomDev::ChefSpec::Configuration.callback(EcomDev::ChefSpec::Stub::IncludeRecipe)
@@ -0,0 +1,86 @@
1
+ module EcomDev
2
+ module ChefSpec
3
+ class Configuration
4
+ class << self
5
+ extend Forwardable
6
+ def_delegators :instance, :reset, :setup!, :teardown!, :cookbook_path, :callback
7
+ end
8
+
9
+ include Singleton
10
+
11
+ attr_accessor :cookbook_paths
12
+ attr_accessor :callbacks
13
+
14
+ def initialize
15
+ @cookbook_paths = []
16
+ @callbacks = []
17
+ end
18
+
19
+ def setup!
20
+ unless cookbook_paths.empty?
21
+ original_path = RSpec.configuration.cookbook_path
22
+ if original_path.nil?
23
+ original_path = []
24
+ elsif original_path.is_a?(String)
25
+ original_path = [original_path]
26
+ end
27
+
28
+ cookbook_paths.each do |path|
29
+ original_path << path unless original_path.include?(path)
30
+ end
31
+
32
+ RSpec.configuration.cookbook_path = original_path
33
+ end
34
+
35
+ invoke_callbacks(__method__)
36
+ end
37
+
38
+ def teardown!
39
+ invoke_callbacks(__method__)
40
+ end
41
+
42
+ def reset
43
+ @cookbook_paths = []
44
+ @callbacks = []
45
+ end
46
+
47
+ def cookbook_path(path)
48
+ @cookbook_paths << path unless cookbook_paths.include?(path)
49
+ end
50
+
51
+ def callback(callback)
52
+ @callbacks << callback unless callbacks.include?(callback)
53
+ end
54
+
55
+ def self.register
56
+ if defined?(ChefSpec::Berkshelf)
57
+ klass = ChefSpec::Berkshelf
58
+ elsif defined?(ChefSpec::Librarian)
59
+ klass = ChefSpec::Librarian
60
+ else
61
+ klass = false
62
+ end
63
+
64
+ if klass
65
+ klass.class_exec do
66
+ alias_method :old_setup!, :setup!
67
+
68
+ def setup!
69
+ old_setup!
70
+ EcomDev::ChefSpec::Configuration.setup!
71
+ end
72
+ end
73
+ else
74
+ RSpec.configure do
75
+ before(:suite) { EcomDev::ChefSpec::Configuration.setup! }
76
+ end
77
+ end
78
+ end
79
+
80
+ private
81
+ def invoke_callbacks(method)
82
+ callbacks.select { |c| c.respond_to?(method) }.each { |c| c.send(method) }
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,126 @@
1
+ require_relative 'matcher/dsl'
2
+ require_relative 'matcher/helper'
3
+
4
+ module EcomDev
5
+ module ChefSpec
6
+ module Resource
7
+ class Matcher
8
+ class << self
9
+ extend Forwardable
10
+ def_delegators :instance, :setup!, :teardown!, :register, :matcher, :runner
11
+ end
12
+
13
+ include Singleton
14
+
15
+ attr_reader :matchers, :runners
16
+ attr_accessor :possible_directories
17
+
18
+ def initialize
19
+ @matchers = {}
20
+ @runners = []
21
+ @possible_directories = [
22
+ File.join('spec', 'matchers.rb'),
23
+ File.join('spec', 'matchers', '*.rb'),
24
+ File.join('spec', 'libraries', 'matchers.rb'),
25
+ File.join('spec', 'libraries', 'matchers/*.rb')
26
+ ]
27
+ end
28
+
29
+ def setup!
30
+ load_matchers
31
+ extend_api
32
+ end
33
+
34
+ def teardown!
35
+ @matchers = {}
36
+ @runners = []
37
+ Helper.reset
38
+ end
39
+
40
+ def extend_api
41
+ matchers.each do |method, info|
42
+ Helper.add(method) do |identity|
43
+ ChefSpec::Matchers::ResourceMatcher.new(info.resource, info.action, identity)
44
+ end
45
+ end
46
+ end
47
+
48
+ def matcher(resource_name, action)
49
+ if resource_name.is_a?(Array)
50
+ resource_name.each do |r|
51
+ matcher(r, action)
52
+ end
53
+ elsif action.is_a?(Array)
54
+ action.each do |a|
55
+ matcher(resource_name, a)
56
+ end
57
+ else
58
+ add_matcher(resource_name, action)
59
+ end
60
+ end
61
+
62
+ def runner(resource_name)
63
+ if resource_name.is_a?(Array)
64
+ resource_name.each do |r|
65
+ runner(r)
66
+ end
67
+ else
68
+ add_runner(resource_name)
69
+ end
70
+ end
71
+
72
+ def load_matchers
73
+ files = search_patterns.map { |pattern| Dir.glob(pattern) }.flatten
74
+ files.each {|file| load_matcher_file(file) }
75
+ end
76
+
77
+ def load_matcher_file(file)
78
+ DSL.load(file)
79
+ end
80
+
81
+ def search_patterns
82
+ cookbook_path = RSpec.configuration.cookbook_path
83
+ if cookbook_path.nil? ||
84
+ cookbook_path.is_a?(String) && cookbook_path.empty?
85
+ return []
86
+ end
87
+
88
+ cookbook_path = [cookbook_path] if cookbook_path.is_a?(String)
89
+
90
+ search_patterns = []
91
+ possible_directories.each do |directory|
92
+ cookbook_path.each do |cookbook_path|
93
+ search_patterns << File.join(cookbook_path, '*', directory)
94
+ end
95
+ end
96
+ search_patterns
97
+ end
98
+
99
+ def register
100
+ EcomDev::ChefSpec::Configuration.callback(self.class.instance)
101
+
102
+ RSpec.configure do |config|
103
+ config.include Helper
104
+ end
105
+ end
106
+
107
+ private
108
+ def add_matcher(resource, action)
109
+ resource_name = resource.to_s
110
+ action_name = action.to_s
111
+ matcher_name = action_name + '_' + resource_name
112
+ matcher = matcher_name.to_sym
113
+ unless @matchers.key?(matcher)
114
+ @matchers[matcher] = {action: action_name.to_sym, resource: resource_name.to_sym}
115
+ end
116
+ end
117
+
118
+ def add_runner(resource_name)
119
+ resource = resource_name.to_sym
120
+ @runners << resource unless @runners.include?(resource)
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end
126
+
@@ -0,0 +1,23 @@
1
+ module EcomDev
2
+ module ChefSpec
3
+ module Resource
4
+ class Matcher
5
+ class DSL
6
+ def matcher(resource, action)
7
+ EcomDev::ChefSpec::Resource::Matcher.matcher(resource, action)
8
+ end
9
+
10
+ def runner(resource)
11
+ EcomDev::ChefSpec::Resource::Matcher.runner(resource)
12
+ end
13
+
14
+ def self.load(filename)
15
+ dsl = new
16
+ dsl.instance_eval(File.read(filename), filename)
17
+ dsl
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,17 @@
1
+ module EcomDev
2
+ module ChefSpec
3
+ module Resource
4
+ class Matcher
5
+ module Helper
6
+ def self.reset
7
+ instance_methods.each { |method| remove_method(method) }
8
+ end
9
+
10
+ def self.add(method, &block)
11
+ define_method(method, &block)
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,71 @@
1
+ module EcomDev::ChefSpec::Stub
2
+ class IncludeRecipe
3
+ class << self
4
+ extend Forwardable
5
+ def_delegators :instance, :teardown!, :setup!, :allow_recipe, :reset
6
+ end
7
+
8
+ include Singleton
9
+
10
+ attr_accessor :allowed_recipes, :loaded_recipes
11
+
12
+ def initialize
13
+ @allowed_recipes = []
14
+ @loaded_recipes = []
15
+ end
16
+
17
+ def allow_recipe(recipe)
18
+ @allowed_recipes << recipe
19
+ end
20
+
21
+ def reset
22
+ @allowed_recipes = []
23
+ @loaded_recipes = []
24
+ end
25
+
26
+ def before_example(object)
27
+ stub_include(object) unless allowed_recipes.empty?
28
+ end
29
+
30
+ def stub_include(object)
31
+ # Don't worry about external cookbook dependencies
32
+ object.allow_any_instance_of(Chef::Cookbook::Metadata).to object.receive(:depends)
33
+
34
+ # Test each recipe in isolation, regardless of includes
35
+ object.allow_any_instance_of(Chef::RunContext).to object.receive(:loaded_recipe?) do |run_context, recipe_name|
36
+ run_context.loaded_recipes.include?(recipe_name)
37
+ end
38
+
39
+ object.allow_any_instance_of(Chef::RunContext).to object.receive(:include_recipe) do |run_context, *recipe_names|
40
+ recipe_names.flatten.each do |recipe_name|
41
+ @loaded_recipes << recipe_name
42
+ if allowed_recipes.include?(recipe_name)
43
+ run_context.load_recipe(recipe_name)
44
+ end
45
+ end
46
+ loaded_recipes
47
+ end
48
+
49
+ object.allow_any_instance_of(Chef::RunContext).to object.receive(:loaded_recipes) do
50
+ loaded_recipes
51
+ end
52
+ end
53
+
54
+ def after_example(object)
55
+ reset
56
+ end
57
+ end
58
+ end
59
+
60
+ RSpec.configure do |c|
61
+ c.before(:each) do
62
+ if respond_to?(:described_recipe) && described_recipe.match(/^[a-z_0-9]+::[a-z_0-9]+$/)
63
+ EcomDev::ChefSpec::Stub::IncludeRecipe.allow_recipe described_recipe
64
+ end
65
+ EcomDev::ChefSpec::Stub::IncludeRecipe.instance.before_example(self)
66
+ end
67
+
68
+ c.after(:each) do
69
+ EcomDev::ChefSpec::Stub::IncludeRecipe.instance.after_example(self)
70
+ end
71
+ end
@@ -0,0 +1,6 @@
1
+
2
+ module EcomDev
3
+ module ChefSpec
4
+ VERSION = '0.1.0'
5
+ end
6
+ end
@@ -0,0 +1,7 @@
1
+ require 'ecomdev-chefspec'
2
+
3
+ RSpec.configure do |config|
4
+ config.expect_with(:rspec) { |c| c.syntax = :expect }
5
+ config.filter_run(focus: true)
6
+ config.run_all_when_everything_filtered = true
7
+ end
@@ -0,0 +1,138 @@
1
+ require 'spec_helper'
2
+
3
+ describe EcomDev::ChefSpec::Configuration do
4
+ before(:each) do
5
+ Singleton.__init__(described_class)
6
+ end
7
+
8
+ def callback_klass
9
+ Class.new
10
+ end
11
+
12
+ describe '#cookbook_path' do
13
+ it 'it adds a new cookbook path to path stack' do
14
+ described_class.cookbook_path('test/path')
15
+ expect(described_class.instance.cookbook_paths).to contain_exactly('test/path')
16
+ end
17
+
18
+ it 'it adds cookbook path only once' do
19
+ described_class.cookbook_path('test/path')
20
+ described_class.cookbook_path('test/path')
21
+ expect(described_class.instance.cookbook_paths).to contain_exactly('test/path')
22
+ end
23
+ end
24
+
25
+ describe '#callback' do
26
+ it 'it adds a new callback' do
27
+ callback = callback_klass.new
28
+ described_class.callback(callback)
29
+ expect(described_class.instance.callbacks).to contain_exactly(callback)
30
+ end
31
+
32
+ it 'it adds a new callback only once' do
33
+ callback = callback_klass.new
34
+ described_class.callback(callback)
35
+ described_class.callback(callback)
36
+ callback2 = callback_klass.new
37
+ described_class.callback(callback2)
38
+ expect(described_class.instance.callbacks).to contain_exactly(callback, callback2)
39
+ end
40
+ end
41
+
42
+ describe '#reset' do
43
+ it 'it removes all callbacks and cookbook_paths' do
44
+ described_class.cookbook_path('test')
45
+ described_class.cookbook_path('test2')
46
+ described_class.callback(callback_klass.new)
47
+ described_class.callback(callback_klass.new)
48
+
49
+ expect(described_class.instance.callbacks).not_to be_empty
50
+ expect(described_class.instance.cookbook_paths).not_to be_empty
51
+
52
+ described_class.reset
53
+
54
+ expect(described_class.instance.callbacks).to be_empty
55
+ expect(described_class.instance.cookbook_paths).to be_empty
56
+ end
57
+ end
58
+
59
+ describe '#setup!' do
60
+ it 'modifies cookbook path for RSpec configuration' do
61
+ described_class.cookbook_path('test')
62
+ described_class.cookbook_path('test2')
63
+
64
+ expect(RSpec.configuration).to receive(:cookbook_path).and_return(nil)
65
+ expect(RSpec.configuration).to receive(:cookbook_path=).with(%w(test test2))
66
+
67
+ described_class.setup!
68
+ end
69
+
70
+ it 'add cookbook_paths after previously defined value' do
71
+ described_class.cookbook_path('test')
72
+ described_class.cookbook_path('test2')
73
+
74
+ expect(RSpec.configuration).to receive(:cookbook_path).and_return('value')
75
+ expect(RSpec.configuration).to receive(:cookbook_path=).with(%w(value test test2))
76
+
77
+ described_class.setup!
78
+ end
79
+
80
+ it 'merges cookbook_path with previously defined values' do
81
+ described_class.cookbook_path('test')
82
+ described_class.cookbook_path('test2')
83
+ described_class.cookbook_path('value3')
84
+
85
+ expect(RSpec.configuration).to receive(:cookbook_path).and_return(%w(value value2 value3))
86
+ expect(RSpec.configuration).to receive(:cookbook_path=).with(%w(value value2 value3 test test2))
87
+
88
+ described_class.setup!
89
+ end
90
+
91
+
92
+ it 'does not modify cookbook path if it is empty' do
93
+ expect(RSpec.configuration).not_to receive(:cookbook_path)
94
+
95
+ described_class.setup!
96
+ end
97
+
98
+ it 'calls a callback method setup! if it exists' do
99
+ callback = double('callback')
100
+
101
+ allow(callback).to receive(:respond_to?).with(:setup!).and_return(true)
102
+ expect(callback).to receive(:setup!)
103
+ described_class.callback(callback)
104
+ described_class.setup!
105
+ end
106
+
107
+ it 'does not call a callback method setup! if it is not defined' do
108
+ callback = double('callback')
109
+
110
+ allow(callback).to receive(:respond_to?).with(:setup!).and_return(false)
111
+ expect(callback).not_to receive(:setup!)
112
+
113
+ described_class.callback(callback)
114
+ described_class.setup!
115
+ end
116
+ end
117
+
118
+ describe '#teardown!' do
119
+ it 'calls a callback method teardown! if it exists' do
120
+ callback = double('callback')
121
+
122
+ allow(callback).to receive(:respond_to?).with(:teardown!).and_return(true)
123
+ expect(callback).to receive(:teardown!)
124
+ described_class.callback(callback)
125
+ described_class.teardown!
126
+ end
127
+
128
+ it 'does not call a callback method teardown! if it is not defined' do
129
+ callback = double('callback')
130
+
131
+ allow(callback).to receive(:respond_to?).with(:teardown!).and_return(false)
132
+ expect(callback).not_to receive(:teardown!)
133
+
134
+ described_class.callback(callback)
135
+ described_class.teardown!
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,45 @@
1
+ require 'spec_helper'
2
+
3
+ describe EcomDev::ChefSpec::Resource::Matcher::DSL do
4
+ let(:instance) { described_class.new }
5
+ let(:matcher) { EcomDev::ChefSpec::Resource::Matcher }
6
+
7
+ describe '#matcher' do
8
+ it 'calls matcher method on resource matcher instance' do
9
+ expect_any_instance_of(matcher).to receive(:matcher).with(:my_matcher, :create)
10
+ instance.matcher(:my_matcher, :create)
11
+ end
12
+
13
+ it 'calls matcher method on resource matcher instance with array arguments' do
14
+ expect_any_instance_of(matcher).to receive(:matcher).with([:my_matcher, :my_matcher2], [:create, :delete])
15
+ instance.matcher([:my_matcher, :my_matcher2], [:create, :delete])
16
+ end
17
+ end
18
+
19
+ describe '#runner' do
20
+ it 'calls runner method on resource matcher instance' do
21
+ expect_any_instance_of(matcher).to receive(:runner).with(:my_matcher)
22
+ instance.runner(:my_matcher)
23
+ end
24
+
25
+ it 'calls runner method on resource matcher instance with array arguments' do
26
+ expect_any_instance_of(matcher).to receive(:runner).with([:my_matcher, :my_matcher2])
27
+ instance.runner([:my_matcher, :my_matcher2])
28
+ end
29
+ end
30
+
31
+ describe '#load' do
32
+ it 'loads file dsl content' do
33
+ allow(File).to receive(:read).with('file.rb').and_return('
34
+ runner :my_custom_runner
35
+ matcher :my_custom_action, :create
36
+ matcher :another_action, [:create, :delete]')
37
+
38
+ expect_any_instance_of(matcher).to receive(:runner).with(:my_custom_runner)
39
+ expect_any_instance_of(matcher).to receive(:matcher).with(:my_custom_action, :create)
40
+ expect_any_instance_of(matcher).to receive(:matcher).with(:another_action, [:create, :delete])
41
+
42
+ expect(described_class.load('file.rb')).to be_instance_of(described_class)
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,58 @@
1
+ require 'spec_helper'
2
+
3
+ describe EcomDev::ChefSpec::Resource::Matcher::Helper do
4
+
5
+ def create_method_body
6
+ Proc.new do |argument|
7
+ argument*argument
8
+ end
9
+ end
10
+
11
+ describe '#add' do
12
+ after(:each) { described_class.reset }
13
+
14
+ it 'it adds a new method' do
15
+ body = create_method_body
16
+
17
+ described_class.add(:my_custom_method, &body)
18
+ described_class.add(:another_custom_method, &body)
19
+
20
+ expect(described_class.instance_methods).to contain_exactly(:my_custom_method, :another_custom_method)
21
+ end
22
+
23
+ it 'it adds a new method only once' do
24
+ body = create_method_body do |arg|
25
+ arg
26
+ end
27
+ body2 = create_method_body do |arg|
28
+ arg*2
29
+ end
30
+
31
+ described_class.add(:another_custom_method, &body)
32
+ described_class.add(:another_custom_method, &body2)
33
+
34
+ expect(described_class.instance_methods).to contain_exactly(:another_custom_method)
35
+ klass = described_class
36
+ callable = Class.new do
37
+ include klass
38
+ end
39
+ expect(callable.new.another_custom_method(1)).to equal(1)
40
+ end
41
+ end
42
+
43
+ describe '#reset' do
44
+ it 'it removes all defined methods for instance' do
45
+ body = create_method_body
46
+
47
+ expect(described_class.instance_methods).to be_empty
48
+
49
+ described_class.add(:my_custom_method, &body)
50
+ described_class.add(:another_custom_method, &body)
51
+
52
+ expect(described_class.instance_methods).to contain_exactly(:my_custom_method, :another_custom_method)
53
+
54
+ described_class.reset
55
+ expect(described_class.instance_methods).to be_empty
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,250 @@
1
+ require 'spec_helper'
2
+
3
+ describe EcomDev::ChefSpec::Resource::Matcher do
4
+ before(:each) do
5
+ Singleton.__init__(described_class)
6
+ end
7
+
8
+ describe '#initialize' do
9
+ it 'should set matchers to empty hash' do
10
+ expect(described_class.instance.matchers).to be_kind_of(Hash).and be_empty
11
+ end
12
+
13
+ it 'should set runners to empty array' do
14
+ expect(described_class.instance.runners).to be_kind_of(Array).and be_empty
15
+ end
16
+
17
+ it 'should set possible_directories to expected list' do
18
+ expect(described_class.instance.possible_directories)
19
+ .to be_kind_of(Array)
20
+ .and contain_exactly(
21
+ File.join('spec', 'matchers.rb'),
22
+ File.join('spec', 'matchers', '*.rb'),
23
+ File.join('spec', 'libraries', 'matchers.rb'),
24
+ File.join('spec', 'libraries', 'matchers', '*.rb')
25
+ )
26
+ end
27
+ end
28
+
29
+ describe '#search_patterns' do
30
+ it 'should include cookbook_path if it is a string' do
31
+ RSpec.configure do |config|
32
+ config.cookbook_path = File.join('test', 'directory')
33
+ end
34
+ expect(described_class.instance.search_patterns)
35
+ .to be_kind_of(Array)
36
+ .and contain_exactly(
37
+ File.join('test', 'directory', '*', 'spec', 'matchers.rb'),
38
+ File.join('test', 'directory', '*', 'spec', 'matchers', '*.rb'),
39
+ File.join('test', 'directory', '*', 'spec', 'libraries', 'matchers.rb'),
40
+ File.join('test', 'directory', '*', 'spec', 'libraries', 'matchers', '*.rb')
41
+ )
42
+ end
43
+
44
+ it 'should include cookbook_path if it is an array' do
45
+ RSpec.configure do |config|
46
+ config.cookbook_path = File.join('test', 'directory')
47
+ end
48
+ expect(described_class.instance.search_patterns)
49
+ .to be_kind_of(Array)
50
+ .and contain_exactly(
51
+ File.join('test', 'directory', '*', 'spec', 'matchers.rb'),
52
+ File.join('test', 'directory', '*', 'spec', 'matchers', '*.rb'),
53
+ File.join('test', 'directory', '*', 'spec', 'libraries', 'matchers.rb'),
54
+ File.join('test', 'directory', '*', 'spec', 'libraries', 'matchers', '*.rb')
55
+ )
56
+ end
57
+
58
+ it 'should not include any cookbook in path if it is nil' do
59
+ RSpec.configure do |config|
60
+ config.cookbook_path = nil
61
+ end
62
+ expect(described_class.instance.search_patterns)
63
+ .to be_kind_of(Array)
64
+ .and be_empty
65
+ end
66
+
67
+ it 'should not include any cookbook in path if it is empty string' do
68
+ RSpec.configure do |config|
69
+ config.cookbook_path = ''
70
+ end
71
+ expect(described_class.instance.search_patterns)
72
+ .to be_kind_of(Array)
73
+ .and be_empty
74
+ end
75
+ end
76
+
77
+ describe '#load_matchers' do
78
+ it 'loads matcher files from configured values' do
79
+ RSpec.configure do |config|
80
+ config.cookbook_path = 'test'
81
+ end
82
+
83
+ matcher_paths = {
84
+ File.join('test', '*', 'matchers.rb') => [
85
+ File.join('test', 'magento', 'matchers.rb'),
86
+ File.join('test', 'nginx', 'matchers.rb')
87
+ ],
88
+ File.join('test', '*', 'matchers', '*.rb') => [
89
+ File.join('test', 'custom', 'matchers', 'matcher1.rb'),
90
+ File.join('test', 'custom', 'matchers', 'matcher2.rb')
91
+ ]
92
+ }
93
+
94
+ matcher_paths.each_pair do |key, value|
95
+ allow(Dir).to receive(:glob).with(key).and_return(value)
96
+ value.each do |file|
97
+ expect_any_instance_of(described_class).to receive(:load_matcher_file).with(file).and_return(true)
98
+ end
99
+ end
100
+
101
+ described_class.instance.possible_directories = ['matchers.rb', File.join('matchers', '*.rb')]
102
+ described_class.instance.load_matchers
103
+ end
104
+ end
105
+
106
+ describe '#load_matcher_file' do
107
+ it 'loads file as matcher DSL' do
108
+ dsl_class = EcomDev::ChefSpec::Resource::Matcher::DSL
109
+ expect(dsl_class).to receive(:load).with('file.rb').and_return(dsl_class.new)
110
+ described_class.instance.load_matcher_file('file.rb')
111
+ end
112
+ end
113
+
114
+ describe '#setup!' do
115
+ it 'loads matcher files and extends api' do
116
+ expect(described_class.instance).to receive(:load_matchers).and_return([])
117
+ expect(described_class.instance).to receive(:extend_api).and_return(described_class.instance)
118
+ described_class.setup!
119
+ end
120
+ end
121
+
122
+ describe '#extend_api' do
123
+ after(:each) { EcomDev::ChefSpec::Resource::Matcher::Helper.reset }
124
+ it 'should add new instance methods to helper' do
125
+ described_class.matcher(:test, :create)
126
+ described_class.instance.extend_api
127
+
128
+ expect(EcomDev::ChefSpec::Resource::Matcher::Helper.instance_methods).to contain_exactly(:create_test)
129
+ end
130
+ end
131
+
132
+ describe '#teardown!' do
133
+ it 'clears matchers and runners' do
134
+ described_class.matcher(:test, :test)
135
+ described_class.runner(:test)
136
+ described_class.setup!
137
+ described_class.teardown!
138
+ expect(described_class.instance.runners).to be_empty
139
+ expect(described_class.instance.matchers).to be_empty
140
+ expect(EcomDev::ChefSpec::Resource::Matcher::Helper.instance_methods).to be_empty
141
+ end
142
+ end
143
+
144
+ describe '#matcher' do
145
+ it 'adds matcher if resource and action are symbols' do
146
+ described_class.matcher(:my_custom_matcher, :create)
147
+ expect(described_class.instance.matchers).to eq(create_my_custom_matcher: {action: :create, resource: :my_custom_matcher})
148
+ end
149
+
150
+ it 'adds matcher if resource and action are strings' do
151
+ described_class.matcher('my_custom_matcher', 'create')
152
+ expect(described_class.instance.matchers).to eq(create_my_custom_matcher: {action: :create, resource: :my_custom_matcher})
153
+ end
154
+
155
+ it 'adds multiple matchers if actions is an array of symbols' do
156
+ described_class.matcher(:my_custom_matcher, [:create, :delete])
157
+ expect(described_class.instance.matchers).to eq(
158
+ create_my_custom_matcher: {
159
+ action: :create,
160
+ resource: :my_custom_matcher
161
+ },
162
+ delete_my_custom_matcher: {
163
+ action: :delete,
164
+ resource: :my_custom_matcher
165
+ }
166
+ )
167
+ end
168
+
169
+ it 'adds multiple matchers if actions is an array of strings' do
170
+ described_class.matcher(:my_custom_matcher, %w(create delete))
171
+ expect(described_class.instance.matchers).to eq(
172
+ create_my_custom_matcher: {
173
+ action: :create,
174
+ resource: :my_custom_matcher
175
+ },
176
+ delete_my_custom_matcher: {
177
+ action: :delete,
178
+ resource: :my_custom_matcher
179
+ }
180
+ )
181
+ end
182
+
183
+ it 'adds multiple matchers if resource is an array' do
184
+ described_class.matcher([:my_custom_matcher, :another_matcher], %w(create delete))
185
+ expect(described_class.instance.matchers).to eq(
186
+ create_my_custom_matcher: {
187
+ action: :create,
188
+ resource: :my_custom_matcher
189
+ },
190
+ delete_my_custom_matcher: {
191
+ action: :delete,
192
+ resource: :my_custom_matcher
193
+ },
194
+ create_another_matcher: {
195
+ action: :create,
196
+ resource: :another_matcher
197
+ },
198
+ delete_another_matcher: {
199
+ action: :delete,
200
+ resource: :another_matcher
201
+ }
202
+ )
203
+ end
204
+
205
+ it 'adds matcher only once' do
206
+ described_class.matcher('my_custom_matcher', 'create')
207
+ described_class.matcher('my_custom_matcher', 'create')
208
+ expect(described_class.instance.matchers).to eq(
209
+ create_my_custom_matcher: {
210
+ action: :create,
211
+ resource: :my_custom_matcher
212
+ }
213
+ )
214
+ end
215
+ end
216
+
217
+ describe '#runner' do
218
+ it 'adds runner if resource is a symbol' do
219
+ described_class.runner(:my_custom_matcher)
220
+ expect(described_class.instance.runners).to contain_exactly(:my_custom_matcher)
221
+ end
222
+
223
+ it 'adds matcher if resource is a string' do
224
+ described_class.runner('my_custom_matcher')
225
+ expect(described_class.instance.runners).to contain_exactly(:my_custom_matcher)
226
+ end
227
+
228
+ it 'adds multiple runners if resource is an array of symbols' do
229
+ described_class.runner([:my_custom_matcher, :antoher_matcher])
230
+ expect(described_class.instance.runners).to contain_exactly(
231
+ :my_custom_matcher,
232
+ :antoher_matcher
233
+ )
234
+ end
235
+
236
+ it 'adds multiple runners if resource is an array of strings' do
237
+ described_class.runner(%w(my_custom_matcher antoher_matcher))
238
+ expect(described_class.instance.runners).to contain_exactly(
239
+ :my_custom_matcher,
240
+ :antoher_matcher
241
+ )
242
+ end
243
+
244
+ it 'adds runner only once' do
245
+ described_class.runner('my_custom_matcher')
246
+ described_class.runner('my_custom_matcher')
247
+ expect(described_class.instance.runners).to contain_exactly(:my_custom_matcher)
248
+ end
249
+ end
250
+ end
@@ -0,0 +1,33 @@
1
+
2
+ describe 'test::test' do
3
+ let (:instance) { EcomDev::ChefSpec::Stub::IncludeRecipe.instance }
4
+
5
+ it 'should add test::test to allowed recipes' do
6
+ expect(instance.allowed_recipes).to contain_exactly('test::test')
7
+ end
8
+
9
+ it 'should have an available allow_recipe method over API' do
10
+ allow_recipe('my_custom::recipe')
11
+ expect(instance.allowed_recipes).to contain_exactly('test::test', 'my_custom::recipe')
12
+ end
13
+
14
+ it 'allow recipe should allow multiple arguments as recipe names' do
15
+ allow_recipe('my_custom::recipe', 'recipe::another')
16
+ expect(instance.allowed_recipes).to contain_exactly('test::test', 'my_custom::recipe', 'recipe::another')
17
+ end
18
+
19
+ it 'allow recipe should allow multiple arguments as recipe names' do
20
+ allow_recipe(%w(my_custom::recipe recipe::another), 'recipe::third')
21
+ expect(instance.allowed_recipes).to contain_exactly('test::test', 'my_custom::recipe', 'recipe::another', 'recipe::third')
22
+ end
23
+
24
+ context 'when defined in hook it' do
25
+ before (:each) { allow_recipe('before::recipe') }
26
+
27
+ it 'calls allow_recipe as well' do
28
+ expect(instance.allowed_recipes).to contain_exactly('test::test', 'before::recipe')
29
+ end
30
+ end
31
+
32
+
33
+ end
@@ -0,0 +1,55 @@
1
+ require 'spec_helper'
2
+
3
+ describe EcomDev::ChefSpec::Stub::IncludeRecipe do
4
+ describe '#allow_recipe' do
5
+ it 'should add recipe to the list of allowed' do
6
+ described_class.allow_recipe('test_my::test')
7
+ described_class.allow_recipe('test_two::test')
8
+ expect(described_class.instance.allowed_recipes).to contain_exactly('test_my::test', 'test_two::test')
9
+ end
10
+ end
11
+
12
+ describe '#each_exameple' do
13
+ context 'when there is no described recipe it' do
14
+ it 'does not stub any include of recipe' do
15
+ expect { Chef::Cookbook::Metadata.new.depends }.to raise_error(ArgumentError)
16
+ end
17
+ end
18
+
19
+ context 'when there is a described recipe', :allow_recipe => true do
20
+ let (:runner) { ChefSpec::Runner.new }
21
+ let (:described_recipe) { 'test::test' }
22
+
23
+ it 'loaded recipes should be empty in the beginning' do
24
+ expect(described_class.instance.loaded_recipes).to be_empty
25
+ end
26
+
27
+ it 'should call original load method on allowed recipes' do
28
+ described_class.allow_recipe('test2::test')
29
+ expect_any_instance_of(Chef::RunContext).to receive(:load_recipe).with('test::test').exactly(1).times
30
+ expect_any_instance_of(Chef::RunContext).to receive(:load_recipe).with('test2::test').exactly(1).times
31
+ expect_any_instance_of(Chef::Client).to receive(:assert_cookbook_path_not_empty)
32
+
33
+ runner.converge
34
+ runner.run_context.include_recipe('test::test')
35
+ runner.run_context.include_recipe('test2::test')
36
+
37
+ expect(runner.run_context.loaded_recipe?('test::test')).to eq(true)
38
+ expect(runner.run_context.loaded_recipe?('test2::test')).to eq(true)
39
+ end
40
+
41
+ it 'should mark as included recipes that are not allowed, but do not load them' do
42
+ allow_any_instance_of(Chef::RunContext).to receive(:load_recipe).and_call_original
43
+ expect_any_instance_of(Chef::RunContext).not_to receive(:load_recipe).with('dummy::test')
44
+ expect_any_instance_of(Chef::Client).to receive(:assert_cookbook_path_not_empty)
45
+
46
+ runner.converge
47
+ runner.run_context.include_recipe('dummy::test')
48
+
49
+ expect(runner.run_context.loaded_recipes).to contain_exactly('dummy::test')
50
+ expect(runner.run_context.loaded_recipe?('dummy::test')).to eq(true)
51
+ expect(runner.run_context.loaded_recipe?('test::test')).to eq(false)
52
+ end
53
+ end
54
+ end
55
+ end
metadata ADDED
@@ -0,0 +1,119 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ecomdev-chefspec
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Ivan Chepurnyi
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-07-17 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: chefspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '4.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '4.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.6'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.6'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: A collection of helpers for chef spec, to make easier writing recipe
56
+ specs
57
+ email:
58
+ - ivan.chepurnyi@ecomdev.org
59
+ executables: []
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - ".gitignore"
64
+ - ".travis.yml"
65
+ - Gemfile
66
+ - LICENSE.txt
67
+ - README.md
68
+ - Rakefile
69
+ - ecomdev-chefspec.gemspec
70
+ - lib/ecomdev-chefspec.rb
71
+ - lib/ecomdev/chefspec.rb
72
+ - lib/ecomdev/chefspec/api.rb
73
+ - lib/ecomdev/chefspec/api/include_recipe.rb
74
+ - lib/ecomdev/chefspec/configuration.rb
75
+ - lib/ecomdev/chefspec/resource/matcher.rb
76
+ - lib/ecomdev/chefspec/resource/matcher/dsl.rb
77
+ - lib/ecomdev/chefspec/resource/matcher/helper.rb
78
+ - lib/ecomdev/chefspec/stub/include_recipe.rb
79
+ - lib/ecomdev/chefspec/version.rb
80
+ - spec/spec_helper.rb
81
+ - spec/unit/configuration_spec.rb
82
+ - spec/unit/matcher/dsl_spec.rb
83
+ - spec/unit/matcher/helper_spec.rb
84
+ - spec/unit/matcher_spec.rb
85
+ - spec/unit/stub/include_recipe/test_spec.rb
86
+ - spec/unit/stub/include_recipe_spec.rb
87
+ homepage: https://github.com/IvanChepurnyi/ecomdev-chefspec
88
+ licenses:
89
+ - GPLv3
90
+ metadata: {}
91
+ post_install_message:
92
+ rdoc_options: []
93
+ require_paths:
94
+ - lib
95
+ required_ruby_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ required_rubygems_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ requirements: []
106
+ rubyforge_project:
107
+ rubygems_version: 2.4.0
108
+ signing_key:
109
+ specification_version: 4
110
+ summary: A collection of helpers for chef spec, to make easier writing recipe specs
111
+ test_files:
112
+ - spec/spec_helper.rb
113
+ - spec/unit/configuration_spec.rb
114
+ - spec/unit/matcher/dsl_spec.rb
115
+ - spec/unit/matcher/helper_spec.rb
116
+ - spec/unit/matcher_spec.rb
117
+ - spec/unit/stub/include_recipe/test_spec.rb
118
+ - spec/unit/stub/include_recipe_spec.rb
119
+ has_rdoc: