ecomdev-chefspec 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: