halite 1.0.0.rc.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +3 -0
  3. data/.travis.yml +10 -0
  4. data/Gemfile +15 -0
  5. data/LICENSE +202 -0
  6. data/README.md +75 -0
  7. data/Rakefile +22 -0
  8. data/halite.gemspec +33 -0
  9. data/lib/berkshelf/halite.rb +2 -0
  10. data/lib/halite.rb +12 -0
  11. data/lib/halite/berkshelf/helper.rb +69 -0
  12. data/lib/halite/berkshelf/source.rb +56 -0
  13. data/lib/halite/converter.rb +17 -0
  14. data/lib/halite/converter/libraries.rb +40 -0
  15. data/lib/halite/converter/metadata.rb +21 -0
  16. data/lib/halite/converter/other.rb +19 -0
  17. data/lib/halite/converter/readme.rb +20 -0
  18. data/lib/halite/dependencies.rb +72 -0
  19. data/lib/halite/error.rb +4 -0
  20. data/lib/halite/gem.rb +82 -0
  21. data/lib/halite/rake_helper.rb +151 -0
  22. data/lib/halite/rake_tasks.rb +2 -0
  23. data/lib/halite/spec_helper.rb +134 -0
  24. data/lib/halite/spec_helper/empty/README.md +1 -0
  25. data/lib/halite/spec_helper/runner.rb +43 -0
  26. data/lib/halite/version.rb +3 -0
  27. data/spec/converter/libraries_spec.rb +152 -0
  28. data/spec/converter/metadata_spec.rb +60 -0
  29. data/spec/converter/other_spec.rb +56 -0
  30. data/spec/converter/readme_spec.rb +55 -0
  31. data/spec/converter_spec.rb +14 -0
  32. data/spec/data/gems/test1/Rakefile +1 -0
  33. data/spec/data/gems/test1/lib/test1.rb +2 -0
  34. data/spec/data/gems/test1/lib/test1/version.rb +3 -0
  35. data/spec/data/gems/test1/test1.gemspec +25 -0
  36. data/spec/data/gems/test2/Rakefile +1 -0
  37. data/spec/data/gems/test2/chef/attributes.rb +0 -0
  38. data/spec/data/gems/test2/chef/recipes/default.rb +0 -0
  39. data/spec/data/gems/test2/chef/templates/default/conf.erb +0 -0
  40. data/spec/data/gems/test2/lib/test2.rb +4 -0
  41. data/spec/data/gems/test2/lib/test2/resource.rb +6 -0
  42. data/spec/data/gems/test2/lib/test2/version.rb +3 -0
  43. data/spec/data/gems/test2/test2.gemspec +24 -0
  44. data/spec/data/gems/test3/Rakefile +1 -0
  45. data/spec/data/gems/test3/chef/recipes/default.rb +1 -0
  46. data/spec/data/gems/test3/lib/test3.rb +4 -0
  47. data/spec/data/gems/test3/lib/test3/dsl.rb +15 -0
  48. data/spec/data/gems/test3/lib/test3/version.rb +3 -0
  49. data/spec/data/gems/test3/test3.gemspec +24 -0
  50. data/spec/data/integration_cookbooks/test1/libraries/test1.rb +3 -0
  51. data/spec/data/integration_cookbooks/test1/libraries/test1__version.rb +4 -0
  52. data/spec/data/integration_cookbooks/test1/metadata.rb +4 -0
  53. data/spec/data/integration_cookbooks/test2/attributes.rb +0 -0
  54. data/spec/data/integration_cookbooks/test2/libraries/test2.rb +5 -0
  55. data/spec/data/integration_cookbooks/test2/libraries/test2__resource.rb +7 -0
  56. data/spec/data/integration_cookbooks/test2/libraries/test2__version.rb +4 -0
  57. data/spec/data/integration_cookbooks/test2/metadata.rb +4 -0
  58. data/spec/data/integration_cookbooks/test2/recipes/default.rb +0 -0
  59. data/spec/data/integration_cookbooks/test2/templates/default/conf.erb +0 -0
  60. data/spec/data/integration_cookbooks/test3/libraries/test3.rb +5 -0
  61. data/spec/data/integration_cookbooks/test3/libraries/test3__dsl.rb +16 -0
  62. data/spec/data/integration_cookbooks/test3/libraries/test3__version.rb +4 -0
  63. data/spec/data/integration_cookbooks/test3/metadata.rb +4 -0
  64. data/spec/data/integration_cookbooks/test3/recipes/default.rb +1 -0
  65. data/spec/dependencies_spec.rb +167 -0
  66. data/spec/gem_spec.rb +164 -0
  67. data/spec/integration_spec.rb +104 -0
  68. data/spec/spec_helper.rb +28 -0
  69. metadata +307 -0
@@ -0,0 +1,2 @@
1
+ require 'halite/rake_helper'
2
+ ::Halite::RakeHelper.install_tasks
@@ -0,0 +1,134 @@
1
+ #
2
+ # Copyright 2015, Noah Kantrowitz
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ require 'halite/spec_helper/runner'
18
+
19
+ module Halite
20
+ module SpecHelper
21
+ extend RSpec::SharedContext
22
+ let(:step_into) { [] }
23
+
24
+ # An alias for slightly more semantic meaning, just forces the lazy #subject to run.
25
+ def run_chef
26
+ subject
27
+ end
28
+
29
+ private
30
+
31
+ def patch_module(mod, name, obj, &block)
32
+ class_name = Chef::Mixin::ConvertToClassName.convert_to_class_name(name.to_s)
33
+ if mod.const_defined?(class_name, false)
34
+ old_class = mod.const_get(class_name, false)
35
+ # We are only allowed to patch over things installed by patch_module
36
+ raise "#{mod.name}::#{class_name} is already defined" if !old_class.instance_variable_get(:@poise_spec_helper)
37
+ # Remove it before setting to avoid the redefinition warning
38
+ mod.send(:remove_const, class_name)
39
+ end
40
+ # Tag our objects so we know we are allows to overwrite those, but not other stuff.
41
+ obj.instance_variable_set(:@poise_spec_helper, true)
42
+ mod.const_set(class_name, obj)
43
+ begin
44
+ block.call
45
+ ensure
46
+ # Same as above, have to remove before set because warnings
47
+ mod.send(:remove_const, class_name)
48
+ mod.const_set(class_name, old_class) if old_class
49
+ end
50
+ end
51
+
52
+ module ClassMethods
53
+ def recipe(&block)
54
+ # Keep the actual logic in a let in case I want to define the subject as something else
55
+ let(:chef_run) { Halite::SpecHelper::Runner.new(step_into: step_into).converge(&block) }
56
+ subject { chef_run }
57
+ end
58
+
59
+ # A note about the :parent option below: You can't use resources that
60
+ # are defined via these helpers because they don't have a global const
61
+ # name until the actual tests are executing.
62
+
63
+ def resource(name, options={}, &block)
64
+ options = {auto: true, parent: Chef::Resource}.merge(options)
65
+ # Create the resource class
66
+ resource_class = Class.new(options[:parent]) do
67
+ class_exec(&block) if block
68
+ # Wrap some stuff around initialize because I'm lazy
69
+ if options[:auto]
70
+ old_init = instance_method(:initialize)
71
+ define_method(:initialize) do |*args|
72
+ # Fill in the resource name because I know it
73
+ @resource_name = name.to_sym
74
+ old_init.bind(self).call(*args)
75
+ # ChefSpec doesn't seem to work well with action :nothing
76
+ if @action == :nothing
77
+ @action = :run
78
+ @allowed_actions |= [:run]
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ # Figure out the available actions
85
+ resource_class.new(nil, nil).allowed_actions.each do |action|
86
+ define_method("#{action}_#{name}") do |resource_name|
87
+ ChefSpec::Matchers::ResourceMatcher.new(name, action, resource_name)
88
+ end
89
+ end
90
+
91
+ around do |ex|
92
+ # Automatically step in to our new resource
93
+ step_into << name
94
+ # Patch the resource in to Chef
95
+ patch_module(Chef::Resource, name, resource_class) { ex.run }
96
+ end
97
+ end
98
+
99
+ def provider(name, options={}, &block)
100
+ options = {auto: true, rspec: true, parent: Chef::Provider}.merge(options)
101
+ provider_class = Class.new(options[:parent]) do
102
+ # Pull in RSpec expectations
103
+ if options[:rspec]
104
+ include RSpec::Matchers
105
+ include RSpec::Mocks::ExampleMethods
106
+ end
107
+
108
+ if options[:auto]
109
+ # Default blank impl to avoid error
110
+ def load_current_resource
111
+ end
112
+
113
+ # Blank action because I do that so much
114
+ def action_run
115
+ end
116
+ end
117
+
118
+ class_exec(&block) if block
119
+ end
120
+
121
+ around do |ex|
122
+ patch_module(Chef::Provider, name, provider_class) { ex.run }
123
+ end
124
+ end
125
+
126
+ def included(klass)
127
+ super
128
+ klass.extend ClassMethods
129
+ end
130
+ end
131
+
132
+ extend ClassMethods
133
+ end
134
+ end
@@ -0,0 +1 @@
1
+ This folder is intentionally empty except for this README. It is used by Halite::SpecHelper::Runner.
@@ -0,0 +1,43 @@
1
+ #
2
+ # Copyright 2015, Noah Kantrowitz
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ require 'chef/recipe'
18
+
19
+ module Halite
20
+ module SpecHelper
21
+ class Runner < ChefSpec::SoloRunner
22
+ def self.converge(&block)
23
+ new.tap do |instance|
24
+ instance.converge(&block)
25
+ end
26
+ end
27
+
28
+ def converge(&block)
29
+ super do
30
+ recipe = Chef::Recipe.new(nil, nil, run_context)
31
+ recipe.instance_exec(&block)
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ # Don't try to autodetect
38
+ def calling_cookbook_path(kaller)
39
+ File.expand_path('../empty', __FILE__)
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,3 @@
1
+ module Halite
2
+ VERSION = '1.0.0.rc.1'
3
+ end
@@ -0,0 +1,152 @@
1
+ require 'spec_helper'
2
+ require 'halite/converter/libraries'
3
+
4
+ describe Halite::Converter::Libraries do
5
+
6
+ describe '#generate' do
7
+ let(:data) { '' }
8
+ let(:entry_point) { false }
9
+ let(:cookbook_dependencies) { [] }
10
+ subject { described_class.generate(double(name: 'mygem', cookbook_dependencies: cookbook_dependencies.map {|dep| Halite::Dependencies::Dependency.new(dep, nil, :dependencies) }), data, entry_point) }
11
+
12
+ context 'with a single require' do
13
+ let(:data) { "x = 1\nrequire 'mygem/version'\n" }
14
+ it { is_expected.to eq <<-EOH }
15
+ if ENV['HALITE_LOAD']; x = 1
16
+ require_relative 'mygem__version'
17
+ end
18
+ EOH
19
+ end # /context with a single require
20
+
21
+ context 'with two requires' do
22
+ let(:data) { "require 'mygem/foo/bar'\nrequire 'another'" }
23
+ it { is_expected.to eq <<-EOH }
24
+ if ENV['HALITE_LOAD']; require_relative 'mygem__foo__bar'
25
+ require 'another'
26
+ end
27
+ EOH
28
+ end # /context with two requires
29
+
30
+ context 'with an entry point' do
31
+ let(:data) { "x = 1\nrequire 'mygem/version'\n" }
32
+ let(:entry_point) { true }
33
+ it { is_expected.to eq <<-EOH }
34
+ ENV['HALITE_LOAD'] = '1'; begin; x = 1
35
+ require_relative 'mygem__version'
36
+ ensure; ENV.delete('HALITE_LOAD'); end
37
+ EOH
38
+ end # /context with an entry point
39
+
40
+ context 'with a big script' do
41
+ let(:data) { <<-EOH }
42
+ require 'mygem/something'
43
+ require 'mygem/utils'
44
+ require 'activesupport' # ಠ_ಠ
45
+ class Resource
46
+ attribute :source
47
+ end
48
+ EOH
49
+ it { is_expected.to eq <<-EOH }
50
+ if ENV['HALITE_LOAD']; require_relative 'mygem__something'
51
+ require_relative 'mygem__utils'
52
+ require 'activesupport' # ಠ_ಠ
53
+ class Resource
54
+ attribute :source
55
+ end
56
+ end
57
+ EOH
58
+ end # /context with a big script
59
+
60
+ context 'with external dependencies' do
61
+ let(:cookbook_dependencies) { ['other'] }
62
+ let(:data) { <<-EOH }
63
+ require 'mygem/something'
64
+ require 'mygem/utils'
65
+ require "mygem"
66
+ require 'other'
67
+ class Resource
68
+ attribute :source
69
+ end
70
+ EOH
71
+ it { is_expected.to eq <<-EOH }
72
+ if ENV['HALITE_LOAD']; require_relative 'mygem__something'
73
+ require_relative 'mygem__utils'
74
+ require_relative 'mygem'
75
+ require_relative '../../other/libraries/other'
76
+ class Resource
77
+ attribute :source
78
+ end
79
+ end
80
+ EOH
81
+ end # /context with a big script
82
+ end # /describe #generate
83
+
84
+ describe '#write' do
85
+ let(:library_files) { [] }
86
+ let(:output) { [] }
87
+ let(:spec) do
88
+ spec = double(name: 'mygem')
89
+ allow(spec).to receive(:each_library_file) do |&block|
90
+ library_files.each {|path| block.call(File.join('/source', path), path) }
91
+ end
92
+ spec
93
+ end
94
+ before do
95
+ first = true
96
+ library_files.each do |path|
97
+ input_sentinel = double("content of #{path}")
98
+ output_sentinel = double("generated output for #{path}")
99
+ allow(IO).to receive(:read).with(File.join('/source', path)).and_return(input_sentinel)
100
+ allow(described_class).to receive(:generate).with(spec, input_sentinel, first).and_return(output_sentinel)
101
+ first = false
102
+ output << output_sentinel
103
+ end
104
+ allow(File).to receive(:directory?).and_return(false) # Always blank
105
+ end
106
+
107
+ context 'with a single file' do
108
+ let(:library_files) { ['mygem.rb'] }
109
+
110
+ it 'writes a single file' do
111
+ expect(Dir).to receive(:mkdir).with('/test/libraries')
112
+ expect(IO).to receive(:write).with('/test/libraries/mygem.rb', output[0])
113
+ described_class.write(spec, '/test')
114
+ end
115
+ end # /context with a single file
116
+
117
+ context 'with multiple files' do
118
+ let(:library_files) { ['mygem.rb', 'mygem/one.rb', 'mygem/two.rb'] }
119
+
120
+ it 'writes multiple files' do
121
+ expect(Dir).to receive(:mkdir).with('/test/libraries')
122
+ expect(IO).to receive(:write).with('/test/libraries/mygem.rb', output[0])
123
+ expect(IO).to receive(:write).with('/test/libraries/mygem__one.rb', output[1])
124
+ expect(IO).to receive(:write).with('/test/libraries/mygem__two.rb', output[2])
125
+ described_class.write(spec, '/test')
126
+ end
127
+ end # /context with multiple files
128
+
129
+ context 'with an explicit entry point name' do
130
+ let(:library_files) { ['mygem.rb', 'other.rb'] }
131
+
132
+ it 'selects the correct entry point' do
133
+ expect(Dir).to receive(:mkdir).with('/test/libraries')
134
+ expect(IO).to receive(:write).with('/test/libraries/mygem.rb', output[0])
135
+ expect(IO).to receive(:write).with('/test/libraries/other.rb', output[1])
136
+ described_class.write(spec, '/test', 'mygem')
137
+ end
138
+ end # /context with an explicit entry point name
139
+
140
+ context 'with an explicit entry point name ending in .rb' do
141
+ let(:library_files) { ['mygem.rb', 'other.rb'] }
142
+
143
+ it 'selects the correct entry point' do
144
+ expect(Dir).to receive(:mkdir).with('/test/libraries')
145
+ expect(IO).to receive(:write).with('/test/libraries/mygem.rb', output[0])
146
+ expect(IO).to receive(:write).with('/test/libraries/other.rb', output[1])
147
+ described_class.write(spec, '/test', 'mygem.rb')
148
+ end
149
+ end # /context with an explicit entry point name
150
+
151
+ end # /describe #write
152
+ end
@@ -0,0 +1,60 @@
1
+ require 'spec_helper'
2
+ require 'halite/converter/metadata'
3
+ require 'halite/dependencies'
4
+
5
+ describe Halite::Converter::Metadata do
6
+ describe '#generate' do
7
+ let(:gem_name) { 'mygem' }
8
+ let(:cookbook_name) { gem_name }
9
+ let(:version) { '1.0.0' }
10
+ let(:license_header) { '' }
11
+ let(:cookbook_dependencies) { [] }
12
+ let(:spec) { double(name: gem_name, cookbook_name: cookbook_name, version: version, license_header: license_header, cookbook_dependencies: cookbook_dependencies.map {|dep| Halite::Dependencies::Dependency.new(*dep) }) }
13
+ subject { described_class.generate(spec) }
14
+
15
+ context 'with simple data' do
16
+ it { is_expected.to eq <<-EOH }
17
+ name "mygem"
18
+ version "1.0.0"
19
+ EOH
20
+ end # /context with simple data
21
+
22
+ context 'with a license header' do
23
+ let(:license_header) { "# header\n" }
24
+ it { is_expected.to eq <<-EOH }
25
+ # header
26
+ name "mygem"
27
+ version "1.0.0"
28
+ EOH
29
+ end # /context with a license header
30
+
31
+ context 'with one dependency' do
32
+ let(:cookbook_dependencies) { [['other', '>= 0']] }
33
+ it { is_expected.to eq <<-EOH }
34
+ name "mygem"
35
+ version "1.0.0"
36
+ depends "other", ">= 0"
37
+ EOH
38
+ end # /context with one dependency
39
+
40
+ context 'with two dependencies' do
41
+ let(:cookbook_dependencies) { [['other', '~> 1.0'], ['another', '~> 2.0.0']] }
42
+ it { is_expected.to eq <<-EOH }
43
+ name "mygem"
44
+ version "1.0.0"
45
+ depends "other", "~> 1.0"
46
+ depends "another", "~> 2.0.0"
47
+ EOH
48
+ end # /context with two dependencies
49
+ end # /describe #generate
50
+
51
+ describe '#write' do
52
+ let(:output) { double('output') } # sentinel
53
+ before { allow(described_class).to receive(:generate).and_return(output) }
54
+
55
+ it 'should write out metadata' do
56
+ expect(IO).to receive(:write).with('/test/metadata.rb', output)
57
+ described_class.write(nil, '/test')
58
+ end
59
+ end # /describe #write
60
+ end
@@ -0,0 +1,56 @@
1
+ require 'spec_helper'
2
+ require 'halite/converter/other'
3
+
4
+ describe Halite::Converter::Other do
5
+
6
+ describe '#write' do
7
+ let(:files) { [] }
8
+ let(:input) { [] }
9
+ let(:output) { [] }
10
+ let(:spec) do
11
+ spec = double(name: 'mygem')
12
+ allow(spec).to receive(:each_file) do |&block|
13
+ files.each {|path| block.call(File.join('/source', path), path) }
14
+ end
15
+ spec
16
+ end
17
+ before do
18
+ files.each do |path|
19
+ input_sentinel = double("content of #{path}")
20
+ output_sentinel = double("generated output for #{path}")
21
+ allow(File).to receive(:open).with(File.join('/source', path), 'rb').and_yield(input_sentinel)
22
+ input << input_sentinel
23
+ output << output_sentinel
24
+ end
25
+ end
26
+
27
+ context 'with a single file' do
28
+ let(:files) { ['recipes/default.rb'] }
29
+
30
+ it 'writes a single file' do
31
+ expect(FileUtils).to receive(:mkdir_p).with('/test/recipes')
32
+ expect(File).to receive(:open).with('/test/recipes/default.rb', 'wb').and_yield(output[0])
33
+ expect(IO).to receive(:copy_stream).with(input[0], output[0])
34
+ described_class.write(spec, '/test')
35
+ end
36
+ end # /context with a single file
37
+
38
+ context 'with multiple files' do
39
+ let(:files) { ['attributes.rb', 'recipes/default.rb', 'templates/default/conf.erb'] }
40
+
41
+ it 'writes multiple files' do
42
+ expect(FileUtils).to receive(:mkdir_p).with('/test/recipes')
43
+ expect(FileUtils).to receive(:mkdir_p).with('/test/templates/default')
44
+ expect(File).to receive(:open).with('/test/attributes.rb', 'wb').and_yield(output[0])
45
+ expect(IO).to receive(:copy_stream).with(input[0], output[0])
46
+ expect(File).to receive(:open).with('/test/recipes/default.rb', 'wb').and_yield(output[1])
47
+ expect(IO).to receive(:copy_stream).with(input[1], output[1])
48
+ expect(File).to receive(:open).with('/test/templates/default/conf.erb', 'wb').and_yield(output[2])
49
+ expect(IO).to receive(:copy_stream).with(input[2], output[2])
50
+ described_class.write(spec, '/test')
51
+ end
52
+ end # /context with a single file
53
+
54
+ end # /describe #write
55
+
56
+ end