halite 1.0.0.rc.1

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.
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