halite 1.0.0.rc.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/.rubocop.yml +20 -0
  4. data/.travis.yml +14 -4
  5. data/.yardopts +7 -0
  6. data/CHANGELOG.md +5 -0
  7. data/Gemfile +30 -8
  8. data/LICENSE +202 -202
  9. data/README.md +104 -2
  10. data/Rakefile +25 -5
  11. data/gemfiles/default.gemfile +17 -0
  12. data/gemfiles/master.gemfile +21 -0
  13. data/halite.gemspec +21 -10
  14. data/lib/berkshelf/halite.rb +25 -0
  15. data/lib/berkshelf/locations/gem.rb +84 -0
  16. data/lib/halite.rb +34 -4
  17. data/lib/halite/berkshelf/helper.rb +8 -2
  18. data/lib/halite/berkshelf/source.rb +9 -1
  19. data/lib/halite/converter.rb +34 -10
  20. data/lib/halite/converter/chef.rb +43 -0
  21. data/lib/halite/converter/libraries.rb +93 -26
  22. data/lib/halite/converter/metadata.rb +48 -10
  23. data/lib/halite/converter/misc.rb +43 -0
  24. data/lib/halite/dependencies.rb +48 -8
  25. data/lib/halite/error.rb +20 -0
  26. data/lib/halite/gem.rb +106 -21
  27. data/lib/halite/helper_base.rb +129 -0
  28. data/lib/halite/rake_helper.rb +46 -60
  29. data/lib/halite/rake_tasks.rb +17 -1
  30. data/lib/halite/spec_helper.rb +403 -54
  31. data/lib/halite/spec_helper/patcher.rb +130 -0
  32. data/lib/halite/spec_helper/runner.rb +57 -9
  33. data/lib/halite/version.rb +19 -1
  34. data/spec/converter/chef_spec.rb +54 -0
  35. data/spec/converter/libraries_spec.rb +131 -123
  36. data/spec/converter/metadata_spec.rb +61 -8
  37. data/spec/converter/misc_spec.rb +61 -0
  38. data/spec/converter_spec.rb +21 -6
  39. data/spec/dependencies_spec.rb +64 -10
  40. data/spec/example_resources/poise.rb +42 -0
  41. data/spec/example_resources/simple.rb +48 -0
  42. data/spec/{data/gems/test1/lib → fixtures/cookbooks/test1/files/halite_gem}/test1.rb +0 -0
  43. data/spec/{data/gems/test1/lib → fixtures/cookbooks/test1/files/halite_gem}/test1/version.rb +0 -0
  44. data/spec/fixtures/cookbooks/test1/libraries/default.rb +4 -0
  45. data/spec/{data/integration_cookbooks → fixtures/cookbooks}/test1/metadata.rb +0 -0
  46. data/spec/{data/gems/test2/chef → fixtures/cookbooks/test2}/attributes.rb +0 -0
  47. data/spec/{data/gems/test2/lib → fixtures/cookbooks/test2/files/halite_gem}/test2.rb +0 -0
  48. data/spec/{data/gems/test2/lib → fixtures/cookbooks/test2/files/halite_gem}/test2/resource.rb +0 -0
  49. data/spec/{data/gems/test2/lib → fixtures/cookbooks/test2/files/halite_gem}/test2/version.rb +0 -0
  50. data/spec/fixtures/cookbooks/test2/libraries/default.rb +3 -0
  51. data/spec/{data/integration_cookbooks → fixtures/cookbooks}/test2/metadata.rb +1 -1
  52. data/spec/{data/gems/test2/chef → fixtures/cookbooks/test2}/recipes/default.rb +0 -0
  53. data/spec/{data/gems/test2/chef → fixtures/cookbooks/test2}/templates/default/conf.erb +0 -0
  54. data/spec/{data/gems/test3/lib → fixtures/cookbooks/test3/files/halite_gem}/test3.rb +0 -0
  55. data/spec/{data/gems/test3/lib → fixtures/cookbooks/test3/files/halite_gem}/test3/dsl.rb +0 -0
  56. data/spec/{data/gems/test3/lib → fixtures/cookbooks/test3/files/halite_gem}/test3/version.rb +0 -0
  57. data/spec/fixtures/cookbooks/test3/libraries/default.rb +4 -0
  58. data/spec/{data/integration_cookbooks → fixtures/cookbooks}/test3/metadata.rb +0 -0
  59. data/spec/{data/gems/test3/chef → fixtures/cookbooks/test3}/recipes/default.rb +0 -0
  60. data/spec/{data → fixtures}/gems/test1/Rakefile +0 -0
  61. data/spec/fixtures/gems/test1/lib/test1.rb +2 -0
  62. data/spec/fixtures/gems/test1/lib/test1/version.rb +3 -0
  63. data/spec/{data → fixtures}/gems/test1/test1.gemspec +0 -0
  64. data/spec/{data → fixtures}/gems/test2/Rakefile +0 -0
  65. data/spec/{data/integration_cookbooks/test2 → fixtures/gems/test2/chef}/attributes.rb +0 -0
  66. data/spec/{data/integration_cookbooks/test2 → fixtures/gems/test2/chef}/recipes/default.rb +0 -0
  67. data/spec/{data/integration_cookbooks/test2 → fixtures/gems/test2/chef}/templates/default/conf.erb +0 -0
  68. data/spec/fixtures/gems/test2/lib/test2.rb +4 -0
  69. data/spec/{data/integration_cookbooks/test2/libraries/test2__resource.rb → fixtures/gems/test2/lib/test2/resource.rb} +1 -2
  70. data/spec/fixtures/gems/test2/lib/test2/version.rb +3 -0
  71. data/spec/{data → fixtures}/gems/test2/test2.gemspec +0 -0
  72. data/spec/{data → fixtures}/gems/test3/Rakefile +0 -0
  73. data/spec/{data/integration_cookbooks/test3 → fixtures/gems/test3/chef}/recipes/default.rb +0 -0
  74. data/spec/fixtures/gems/test3/lib/test3.rb +4 -0
  75. data/spec/{data/integration_cookbooks/test3/libraries/test3__dsl.rb → fixtures/gems/test3/lib/test3/dsl.rb} +1 -2
  76. data/spec/fixtures/gems/test3/lib/test3/version.rb +3 -0
  77. data/spec/{data → fixtures}/gems/test3/test3.gemspec +1 -0
  78. data/spec/gem_spec.rb +41 -31
  79. data/spec/integration_spec.rb +58 -82
  80. data/spec/runner_spec.rb +108 -0
  81. data/spec/spec_helper.rb +19 -26
  82. data/spec/spec_helper_spec.rb +238 -0
  83. metadata +124 -151
  84. data/lib/halite/converter/other.rb +0 -19
  85. data/lib/halite/converter/readme.rb +0 -20
  86. data/spec/converter/other_spec.rb +0 -56
  87. data/spec/converter/readme_spec.rb +0 -55
  88. data/spec/data/integration_cookbooks/test1/libraries/test1.rb +0 -3
  89. data/spec/data/integration_cookbooks/test1/libraries/test1__version.rb +0 -4
  90. data/spec/data/integration_cookbooks/test2/libraries/test2.rb +0 -5
  91. data/spec/data/integration_cookbooks/test2/libraries/test2__version.rb +0 -4
  92. data/spec/data/integration_cookbooks/test3/libraries/test3.rb +0 -5
  93. data/spec/data/integration_cookbooks/test3/libraries/test3__version.rb +0 -4
@@ -0,0 +1,130 @@
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/resource'
18
+
19
+
20
+ module Halite
21
+ module SpecHelper
22
+ # Utility methods to patch a resource or provider class in to Chef for the
23
+ # duration of a block.
24
+ #
25
+ # @since 1.0.0
26
+ # @api private
27
+ module Patcher
28
+ # Patch a class in to Chef for the duration of a block.
29
+ #
30
+ # @param name [String, Symbol] Name to create in snake-case (eg. :my_name).
31
+ # @param klass [Class] Class to patch in.
32
+ # @param mod [Module] Optional module to create a constant in.
33
+ # @param block [Proc] Block to execute while the patch is available.
34
+ # @return [void]
35
+ def self.patch(name, klass, mod=nil, &block)
36
+ patch_descendants_tracker(klass) do
37
+ patch_node_map(name, klass) do
38
+ patch_recipe_dsl(name, klass) do
39
+ if mod
40
+ patch_module(mod, name, klass, &block)
41
+ else
42
+ block.call
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ # Patch an object in to a global namespace for the duration of a block.
50
+ #
51
+ # @param mod [Module] Namespace to patch in to.
52
+ # @param name [String, Symbol] Name to create in snake-case (eg. :my_name).
53
+ # @param obj Object to patch in.
54
+ # @param block [Proc] Block to execute while the name is available.
55
+ # @return [void]
56
+ def self.patch_module(mod, name, obj, &block)
57
+ class_name = Chef::Mixin::ConvertToClassName.convert_to_class_name(name.to_s)
58
+ if mod.const_defined?(class_name, false)
59
+ old_class = mod.const_get(class_name, false)
60
+ # We are only allowed to patch over things installed by patch_module
61
+ raise "#{mod.name}::#{class_name} is already defined" if !old_class.instance_variable_get(:@poise_patch_module)
62
+ # Remove it before setting to avoid the redefinition warning
63
+ mod.send(:remove_const, class_name)
64
+ end
65
+ # Tag our objects so we know we are allowed to overwrite those, but not other stuff.
66
+ obj.instance_variable_set(:@poise_patch_module, true)
67
+ mod.const_set(class_name, obj)
68
+ begin
69
+ block.call
70
+ ensure
71
+ # Same as above, have to remove before set because warnings
72
+ mod.send(:remove_const, class_name)
73
+ mod.const_set(class_name, old_class) if old_class
74
+ end
75
+ end
76
+
77
+ # Patch an object in to Chef's DescendantsTracker system for the duration
78
+ # of a code block.
79
+ #
80
+ # @param klass [Class] Class to patch in.
81
+ # @param block [Proc] Block to execute while the patch is available.
82
+ # @return [void]
83
+ def self.patch_descendants_tracker(klass, &block)
84
+ begin
85
+ # Re-add to tracking.
86
+ Chef::Mixin::DescendantsTracker.store_inherited(klass.superclass, klass)
87
+ block.call
88
+ ensure
89
+ # Clean up after ourselves.
90
+ Chef::Mixin::DescendantsTracker.direct_descendants(klass.superclass).delete(klass)
91
+ end
92
+ end
93
+
94
+ # Patch a class in to its node_map.
95
+ #
96
+ # @param name [Symbol] Name to patch in.
97
+ # @param klass [Class] Resource class to patch in.
98
+ # @param block [Proc] Block to execute while the patch is available.
99
+ # @return [void]
100
+ def self.patch_node_map(name, klass, &block)
101
+ begin
102
+ # Technically this is set to true on >=12.4, but this should work.
103
+ klass.node_map.set(name, klass)
104
+ block.call
105
+ ensure
106
+ # Sigh.
107
+ klass.node_map.instance_variable_get(:@map).delete(name)
108
+ end
109
+ end
110
+
111
+ # Patch a resource in to Chef's recipe DSL for the duration of a code
112
+ # block. This is a no-op before Chef 12.4.
113
+ #
114
+ # @param name [Symbol] Name to patch in.
115
+ # @param klass [Class] Resource class to patch in.
116
+ # @param block [Proc] Block to execute while the patch is available.
117
+ # @return [void]
118
+ def self.patch_recipe_dsl(name, klass, &block)
119
+ return block.call unless defined?(Chef::DSL::Resources.add_resource_dsl) && klass < Chef::Resource
120
+ begin
121
+ Chef::DSL::Resources.add_resource_dsl(name)
122
+ block.call
123
+ ensure
124
+ Chef::DSL::Resources.remove_resource_dsl(name)
125
+ end
126
+ end
127
+
128
+ end
129
+ end
130
+ end
@@ -14,28 +14,76 @@
14
14
  # limitations under the License.
15
15
  #
16
16
 
17
+ require 'chef/mixin/shell_out' # ಠ_ಠ Missing upstream require
17
18
  require 'chef/recipe'
19
+ require 'chefspec/mixins/normalize' # ಠ_ಠ Missing upstream require
20
+ require 'chefspec/solo_runner'
21
+
22
+ require 'halite/error'
23
+ require 'halite/gem'
24
+
18
25
 
19
26
  module Halite
20
27
  module SpecHelper
28
+ # ChefSpec runner class with Halite customizations. This adds attribute
29
+ # options, Halite synthetic cookbook injection, and block-based recipes.
30
+ #
31
+ # @since 1.0.0
21
32
  class Runner < ChefSpec::SoloRunner
22
- def self.converge(&block)
23
- new.tap do |instance|
24
- instance.converge(&block)
33
+ def self.converge(*recipe_names, &block)
34
+ options = if recipe_names.last.is_a?(Hash)
35
+ # Was called with options
36
+ recipe_names.pop
37
+ else
38
+ {}
39
+ end
40
+ new(options).tap do |instance|
41
+ instance.converge(*recipe_names, &block)
42
+ end
43
+ end
44
+
45
+ def initialize(options={})
46
+ super(options) do |node|
47
+ # Allow inserting arbitrary attribute data in to the node
48
+ node.attributes.default = Chef::Mixin::DeepMerge.merge(node.attributes.default, options[:default_attributes]) if options[:default_attributes]
49
+ node.attributes.normal = Chef::Mixin::DeepMerge.merge(node.attributes.normal, options[:normal_attributes]) if options[:normal_attributes]
50
+ node.attributes.override = Chef::Mixin::DeepMerge.merge(node.attributes.override, options[:override_attributes]) if options[:override_attributes]
51
+ # Store the gemspec for later use
52
+ @halite_gemspec = options[:halite_gemspec]
25
53
  end
26
54
  end
27
55
 
28
- def converge(&block)
29
- super do
30
- recipe = Chef::Recipe.new(nil, nil, run_context)
31
- recipe.instance_exec(&block)
56
+ def converge(*recipe_names, &block)
57
+ raise Halite::Error.new('Cannot pass both recipe names and a recipe block to converge') if !recipe_names.empty? && block
58
+ super(*recipe_names) do
59
+ add_halite_cookbook(node, @halite_gemspec) if @halite_gemspec
60
+ if block
61
+ recipe = Chef::Recipe.new(nil, nil, run_context)
62
+ recipe.instance_exec(&block)
63
+ end
32
64
  end
33
65
  end
34
66
 
35
67
  private
36
68
 
37
- # Don't try to autodetect
38
- def calling_cookbook_path(kaller)
69
+ def add_halite_cookbook(node, gemspec)
70
+ gem_data = Halite::Gem.new(gemspec)
71
+ # Catch any dependency loops.
72
+ return if run_context.cookbook_collection.include?(gem_data.cookbook_name)
73
+ run_context.cookbook_collection[gem_data.cookbook_name] = gem_data.as_cookbook_version
74
+ gem_data.cookbook_dependencies.each do |dep|
75
+ add_halite_cookbook(node, dep.spec) if dep.spec
76
+ end
77
+ # Load attributes if any.
78
+ gem_data.each_file('chef/attributes') do |_full_path, rel_path|
79
+ raise Halite::Error.new("Chef does not support nested attribute files: #{rel_path}") if rel_path.include?(File::SEPARATOR)
80
+ name = File.basename(rel_path, '.rb')
81
+ node.include_attribute("#{gem_data.cookbook_name}::#{name}")
82
+ end
83
+ end
84
+
85
+ # Don't try to autodetect the calling cookbook.
86
+ def calling_cookbook_path(_kaller)
39
87
  File.expand_path('../empty', __FILE__)
40
88
  end
41
89
  end
@@ -1,3 +1,21 @@
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
+
1
18
  module Halite
2
- VERSION = '1.0.0.rc.1'
19
+ # Halite version.
20
+ VERSION = '1.0.0'
3
21
  end
@@ -0,0 +1,54 @@
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 'spec_helper'
18
+
19
+ describe Halite::Converter::Chef do
20
+ describe '#write' do
21
+ let(:files) { [] }
22
+ let(:gem_data) do
23
+ instance_double('Halite::Gem').tap do |d|
24
+ allow(d).to receive(:each_file) do |&block|
25
+ files.each {|path| block.call(File.join('/source', path), path) }
26
+ end
27
+ end
28
+ end
29
+ subject { described_class.write(gem_data, '/test') }
30
+
31
+ context 'with a single file' do
32
+ let(:files) { ['recipes/default.rb'] }
33
+
34
+ it do
35
+ expect(FileUtils).to receive(:mkdir_p).with('/test/recipes')
36
+ expect(FileUtils).to receive(:copy).with('/source/recipes/default.rb', '/test/recipes/default.rb', preserve: true)
37
+ subject
38
+ end
39
+ end # /context with a single file
40
+
41
+ context 'with multiple files' do
42
+ let(:files) { ['attributes.rb', 'recipes/default.rb', 'templates/default/conf.erb'] }
43
+
44
+ it do
45
+ expect(FileUtils).to receive(:mkdir_p).with('/test/recipes')
46
+ expect(FileUtils).to receive(:mkdir_p).with('/test/templates/default')
47
+ expect(FileUtils).to receive(:copy).with('/source/attributes.rb', '/test/attributes.rb', preserve: true)
48
+ expect(FileUtils).to receive(:copy).with('/source/recipes/default.rb', '/test/recipes/default.rb', preserve: true)
49
+ expect(FileUtils).to receive(:copy).with('/source/templates/default/conf.erb', '/test/templates/default/conf.erb', preserve: true)
50
+ subject
51
+ end
52
+ end # /context with multiple files
53
+ end # /describe #write
54
+ end
@@ -1,152 +1,160 @@
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
+
1
17
  require 'spec_helper'
2
18
  require 'halite/converter/libraries'
3
19
 
4
20
  describe Halite::Converter::Libraries do
21
+ describe '#generate_bootstrap' do
22
+ let(:gem_data) { instance_double('Halite::Gem', license_header: '') }
23
+ let(:entry_points) { [] }
24
+ subject { described_class.generate_bootstrap(gem_data, entry_points) }
5
25
 
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" }
26
+ context 'with defaults' do
14
27
  it { is_expected.to eq <<-EOH }
15
- if ENV['HALITE_LOAD']; x = 1
16
- require_relative 'mygem__version'
17
- end
28
+ raise 'Halite is not compatible with no_lazy_load false, please set no_lazy_load true in your Chef configuration file.' unless Chef::Config[:no_lazy_load]
29
+ $LOAD_PATH << File.expand_path('../../files/halite_gem', __FILE__)
18
30
  EOH
19
- end # /context with a single require
31
+ end # /context with defaults
20
32
 
21
- context 'with two requires' do
22
- let(:data) { "require 'mygem/foo/bar'\nrequire 'another'" }
33
+ context 'with a license header' do
34
+ before do
35
+ allow(gem_data).to receive(:license_header).and_return("# Copyright me.\n")
36
+ end
23
37
  it { is_expected.to eq <<-EOH }
24
- if ENV['HALITE_LOAD']; require_relative 'mygem__foo__bar'
25
- require 'another'
26
- end
38
+ # Copyright me.
39
+ raise 'Halite is not compatible with no_lazy_load false, please set no_lazy_load true in your Chef configuration file.' unless Chef::Config[:no_lazy_load]
40
+ $LOAD_PATH << File.expand_path('../../files/halite_gem', __FILE__)
27
41
  EOH
28
- end # /context with two requires
42
+ end
29
43
 
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
44
+ context 'with entry points' do
45
+ let(:entry_points) { %w{mygem/one mygem/two} }
71
46
  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
47
+ raise 'Halite is not compatible with no_lazy_load false, please set no_lazy_load true in your Chef configuration file.' unless Chef::Config[:no_lazy_load]
48
+ $LOAD_PATH << File.expand_path('../../files/halite_gem', __FILE__)
49
+ require "mygem/one"
50
+ require "mygem/two"
80
51
  EOH
81
- end # /context with a big script
82
- end # /describe #generate
52
+ end # /context with entry points
53
+ end # /describe #generate_bootstrap
83
54
 
84
- describe '#write' do
55
+ describe '#write_libraries' do
85
56
  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
57
+ let(:gem_data) do
58
+ instance_double('Halite::Gem').tap do |d|
59
+ allow(d).to receive(:each_library_file) do |&block|
60
+ library_files.each {|path| block.call(File.join('/source', path), path) }
61
+ end
103
62
  end
104
- allow(File).to receive(:directory?).and_return(false) # Always blank
105
63
  end
64
+ subject { described_class.write_libraries(gem_data, '/test') }
106
65
 
107
66
  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')
67
+ let(:library_files) { %w{mygem.rb} }
68
+ it do
69
+ expect(FileUtils).to receive(:mkdir_p).with('/test/files/halite_gem')
70
+ expect(FileUtils).to receive(:copy).with('/source/mygem.rb', '/test/files/halite_gem/mygem.rb', preserve: true)
71
+ subject
114
72
  end
115
73
  end # /context with a single file
116
74
 
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')
75
+ context 'with a multiple files' do
76
+ let(:library_files) { %w{mygem.rb mygem/one.rb mygem/two.rb} }
77
+ it do
78
+ expect(FileUtils).to receive(:mkdir_p).with('/test/files/halite_gem')
79
+ expect(FileUtils).to receive(:mkdir_p).with('/test/files/halite_gem/mygem').twice
80
+ expect(FileUtils).to receive(:copy).with('/source/mygem.rb', '/test/files/halite_gem/mygem.rb', preserve: true)
81
+ expect(FileUtils).to receive(:copy).with('/source/mygem/one.rb', '/test/files/halite_gem/mygem/one.rb', preserve: true)
82
+ expect(FileUtils).to receive(:copy).with('/source/mygem/two.rb', '/test/files/halite_gem/mygem/two.rb', preserve: true)
83
+ subject
126
84
  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'] }
85
+ end # /context with a multiple files
86
+ end # /describe #write_libraries
131
87
 
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')
88
+ describe '#find_default_entry_points' do
89
+ let(:library_files) { [] }
90
+ let(:gem_data) do
91
+ instance_double('Halite::Gem').tap do |d|
92
+ allow(d).to receive(:each_library_file) do |&block|
93
+ library_files.each {|path| block.call(File.join('/source', path), path) }
94
+ end
137
95
  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')
96
+ end
97
+ subject { described_class.find_default_entry_points(gem_data) }
98
+
99
+ context 'with no default entry points' do
100
+ let(:library_files) { %w{mygem.rb} }
101
+ it { is_expected.to eq [] }
102
+ end # /context with no default entry points
103
+
104
+ context 'with a single entry point' do
105
+ let(:library_files) { %w{mygem.rb mygem/cheftie.rb} }
106
+ it { is_expected.to eq ['mygem/cheftie'] }
107
+ end # /context with a single entry point
108
+
109
+ context 'with multiple entry points' do
110
+ let(:library_files) { %w{mygem.rb mygem/cheftie.rb mygem/other.rb mygem/other/cheftie.rb} }
111
+ it { is_expected.to eq ['mygem/cheftie', 'mygem/other/cheftie'] }
112
+ end # /context with multiple entry points
113
+ end # /describe #find_default_entry_points
114
+
115
+ describe '#write_bootstrap' do
116
+ let(:entry_point) { nil }
117
+ let(:spec) { instance_double('Gem::Specification', metadata: {})}
118
+ let(:gem_data) { instance_double('Halite::Gem', spec: spec) }
119
+ let(:output) { double('output sentinel') }
120
+ subject { described_class.write_bootstrap(gem_data, '/test', entry_point) }
121
+
122
+ context 'with defaults' do
123
+ it do
124
+ expect(described_class).to receive(:find_default_entry_points).with(gem_data).and_return([])
125
+ expect(FileUtils).to receive(:mkdir_p).with('/test/libraries')
126
+ expect(described_class).to receive(:generate_bootstrap).with(gem_data, []).and_return(output)
127
+ expect(IO).to receive(:write).with('/test/libraries/default.rb', output)
128
+ subject
148
129
  end
149
- end # /context with an explicit entry point name
130
+ end # /context with defaults
131
+
132
+ context 'with an explicit entry point' do
133
+ let(:entry_point) { 'mygem/one' }
134
+ it do
135
+ expect(FileUtils).to receive(:mkdir_p).with('/test/libraries')
136
+ expect(described_class).to receive(:generate_bootstrap).with(gem_data, ['mygem/one']).and_return(output)
137
+ expect(IO).to receive(:write).with('/test/libraries/default.rb', output)
138
+ subject
139
+ end
140
+ end # /context with an explicit entry point
141
+
142
+ context 'with metadata entry points' do
143
+ it do
144
+ allow(spec).to receive(:metadata).and_return({'halite_entry_point' => 'mygem/one mygem/two'})
145
+ expect(FileUtils).to receive(:mkdir_p).with('/test/libraries')
146
+ expect(described_class).to receive(:generate_bootstrap).with(gem_data, ['mygem/one', 'mygem/two']).and_return(output)
147
+ expect(IO).to receive(:write).with('/test/libraries/default.rb', output)
148
+ subject
149
+ end
150
+ end # /context with metadata entry points
151
+ end # /describe #write_bootstrap
150
152
 
153
+ describe '#write' do
154
+ it do
155
+ expect(described_class).to receive(:write_libraries)
156
+ expect(described_class).to receive(:write_bootstrap)
157
+ described_class.write(nil, nil)
158
+ end
151
159
  end # /describe #write
152
160
  end