busser 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. data/.travis.yml +8 -2
  2. data/CHANGELOG.md +13 -0
  3. data/Gemfile +5 -0
  4. data/busser.gemspec +2 -0
  5. data/features/plugin_create_command.feature +72 -0
  6. data/features/plugin_install_command.feature +3 -2
  7. data/lib/busser.rb +10 -1
  8. data/lib/busser/chef_apply.rb +108 -0
  9. data/lib/busser/chef_ext.rb +52 -0
  10. data/lib/busser/command/plugin.rb +5 -0
  11. data/lib/busser/command/plugin_create.rb +174 -0
  12. data/lib/busser/command/plugin_install.rb +11 -51
  13. data/lib/busser/command/test.rb +24 -0
  14. data/lib/busser/cucumber.rb +10 -0
  15. data/lib/busser/helpers.rb +11 -0
  16. data/lib/busser/rubygems.rb +78 -0
  17. data/lib/busser/runner_plugin/dummy.rb +16 -2
  18. data/lib/busser/version.rb +1 -1
  19. data/spec/busser/helpers_spec.rb +39 -5
  20. data/templates/plugin/CHANGELOG.md.erb +3 -0
  21. data/templates/plugin/Gemfile.erb +3 -0
  22. data/templates/plugin/README.md.erb +41 -0
  23. data/templates/plugin/Rakefile.erb +31 -0
  24. data/templates/plugin/features_env.rb.erb +13 -0
  25. data/templates/plugin/features_plugin_install_command.feature.erb +11 -0
  26. data/templates/plugin/features_plugin_list_command.feature.erb +8 -0
  27. data/templates/plugin/features_test_command.feature.erb +31 -0
  28. data/templates/plugin/gemspec.erb +30 -0
  29. data/templates/plugin/gitignore.erb +17 -0
  30. data/templates/plugin/license_apachev2.erb +15 -0
  31. data/templates/plugin/license_lgplv3.erb +16 -0
  32. data/templates/plugin/license_mit.erb +22 -0
  33. data/templates/plugin/license_reserved.erb +5 -0
  34. data/templates/plugin/runner_plugin.rb.erb +16 -0
  35. data/templates/plugin/tailor.erb +4 -0
  36. data/templates/plugin/travis.yml.erb +11 -0
  37. data/templates/plugin/version.rb.erb +12 -0
  38. metadata +43 -3
@@ -16,7 +16,7 @@
16
16
  # See the License for the specific language governing permissions and
17
17
  # limitations under the License.
18
18
 
19
- require 'rubygems/dependency_installer'
19
+ require 'busser/rubygems'
20
20
  require 'busser/thor'
21
21
 
22
22
  module Busser
@@ -29,14 +29,17 @@ module Busser
29
29
  #
30
30
  class PluginInstall < Busser::Thor::BaseGroup
31
31
 
32
+ include Busser::RubyGems
33
+
32
34
  argument :plugins, :type => :array
33
35
 
34
36
  class_option :force_postinstall, :type => :boolean, :default => false,
35
37
  :desc => "Run the plugin's postinstall if it is already installed"
36
38
 
37
39
  def install_all
38
- silence_gem_ui!
39
- plugins.each { |plugin| install(plugin) }
40
+ silence_gem_ui do
41
+ plugins.each { |plugin| install(plugin) }
42
+ end
40
43
  end
41
44
 
42
45
  private
@@ -45,26 +48,21 @@ module Busser
45
48
  gem_name, version = plugin.split("@")
46
49
  name = gem_name.sub(/^busser-/, '')
47
50
 
48
- if options[:force_postinstall] || install_gem(gem_name, version, name)
51
+ new_install = install_plugin_gem(gem_name, version, name)
52
+
53
+ if options[:force_postinstall] || new_install
49
54
  load_plugin(name)
50
55
  run_postinstall(name)
51
56
  end
52
57
  end
53
58
 
54
- def install_gem(gem, version, name)
55
- install_arg = gem =~ /\.gem$/ ? gem : new_dep(gem, version)
56
-
59
+ def install_plugin_gem(gem, version, name)
57
60
  if internal_plugin?(name) || gem_installed?(gem, version)
58
61
  info "Plugin #{name} already installed"
59
-
60
62
  return false
61
63
  else
62
- spec = dep_installer.install(install_arg).find do |spec|
63
- spec.name == gem
64
- end
65
- Gem.clear_paths
64
+ spec = install_gem(gem, version)
66
65
  info "Plugin #{name} installed (version #{spec.version})"
67
-
68
66
  return true
69
67
  end
70
68
  end
@@ -85,44 +83,6 @@ module Busser
85
83
  spec = Busser::Plugin.gem_from_path(Busser::Plugin.runner_plugin(name))
86
84
  spec && spec.name == "busser"
87
85
  end
88
-
89
- def gem_installed?(name, version)
90
- installed = Array(Gem::Specification.find_all_by_name(name, version))
91
- version = latest_version(name) if version.nil?
92
-
93
- installed.find { |spec| spec.version.to_s == version }
94
- end
95
-
96
- def latest_version(name)
97
- available_gems = dep_installer.find_gems_with_sources(new_dep(name))
98
-
99
- spec, source = if available_gems.respond_to?(:last)
100
- # DependencyInstaller sorts the results such that the last one is
101
- # always the one it considers best.
102
- spec_with_source = available_gems.last
103
- spec_with_source && spec_with_source
104
- else
105
- # Rubygems 2.0 returns a Gem::Available set, which is a
106
- # collection of AvailableSet::Tuple structs
107
- available_gems.pick_best!
108
- best_gem = available_gems.set.first
109
- best_gem && [best_gem.spec, best_gem.source]
110
- end
111
-
112
- spec && spec.version && spec.version.to_s
113
- end
114
-
115
- def silence_gem_ui!
116
- Gem::DefaultUserInteraction.ui = Gem::SilentUI.new
117
- end
118
-
119
- def dep_installer
120
- Gem::DependencyInstaller.new
121
- end
122
-
123
- def new_dep(name, version = nil)
124
- Gem::Dependency.new(name, version)
125
- end
126
86
  end
127
87
  end
128
88
  end
@@ -39,6 +39,7 @@ module Busser
39
39
 
40
40
  banner "Running #{runner} test suite"
41
41
  Busser::Plugin.require!(runner_path)
42
+ prepare_suite(runner)
42
43
  invoke Busser::Plugin.runner_class(klass)
43
44
  end
44
45
  end
@@ -48,6 +49,29 @@ module Busser
48
49
  def skip_runner?(runner)
49
50
  runner == "dummy" && ! Array(plugins).include?("dummy")
50
51
  end
52
+
53
+ def prepare_suite(runner)
54
+ run_prepare_sh(runner)
55
+ run_prepare_recipe(runner)
56
+ end
57
+
58
+ def run_prepare_sh(runner)
59
+ prepare_sh_script = suite_path(runner).join("prepare.sh")
60
+
61
+ if prepare_sh_script.exist?
62
+ banner "Preparing #{runner} suite with #{prepare_sh_script}"
63
+ run!("bash #{prepare_sh_script}")
64
+ end
65
+ end
66
+
67
+ def run_prepare_recipe(runner)
68
+ prepare_recipe = suite_path(runner).join("prepare_recipe.rb")
69
+
70
+ if prepare_recipe.exist?
71
+ banner "Preparing #{runner} suite with #{prepare_recipe}"
72
+ chef_apply(:file => prepare_recipe)
73
+ end
74
+ end
51
75
  end
52
76
  end
53
77
  end
@@ -54,11 +54,21 @@ Given(/^a non bundler environment$/) do
54
54
  end
55
55
  end
56
56
 
57
+ Then(/^the suite directory named "(.*?)" should exist$/) do |name|
58
+ directory = File.join(ENV['BUSSER_ROOT'], "suites", name)
59
+ check_directory_presence([directory], true)
60
+ end
61
+
57
62
  Then(/^the suite directory named "(.*?)" should not exist$/) do |name|
58
63
  directory = File.join(ENV['BUSSER_ROOT'], "suites", name)
59
64
  check_directory_presence([directory], false)
60
65
  end
61
66
 
67
+ Then(/^the suite file "(.*?)" should contain exactly:$/) do |file, content|
68
+ file_name = File.join(ENV['BUSSER_ROOT'], "suites", file)
69
+ check_exact_file_content(file_name, content)
70
+ end
71
+
62
72
  Then(/^a gem named "(.*?)" is installed with version "(.*?)"$/) do |name, ver|
63
73
  unbundlerize do
64
74
  run_simple(unescape("gem list #{name} --version #{ver} -i"), true, nil)
@@ -30,8 +30,19 @@ module Busser
30
30
  path
31
31
  end
32
32
 
33
+ def vendor_path(product = nil)
34
+ path = root_path + "vendor"
35
+ path += product if product
36
+ path
37
+ end
38
+
33
39
  def root_path
34
40
  Pathname.new(ENV['BUSSER_ROOT'] || "/opt/busser")
35
41
  end
42
+
43
+ def chef_apply(config = {}, &block)
44
+ require 'busser/chef_apply'
45
+ ChefApply.new(config, &block).converge
46
+ end
36
47
  end
37
48
  end
@@ -0,0 +1,78 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # Author:: Fletcher Nichol (<fnichol@nichol.ca>)
4
+ #
5
+ # Copyright (C) 2013, Fletcher Nichol
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+
19
+ require 'rubygems/dependency_installer'
20
+
21
+ module Busser
22
+
23
+ module RubyGems
24
+
25
+ module_function
26
+
27
+ def gem_installed?(name, version)
28
+ installed = Array(Gem::Specification.find_all_by_name(name, version))
29
+ version = latest_gem_version(name) if version.nil?
30
+
31
+ installed.find { |spec| spec.version.to_s == version }
32
+ end
33
+
34
+ def install_gem(gem, version)
35
+ install_arg = gem =~ /\.gem$/ ? gem : new_dep(gem, version)
36
+ spec = dep_installer.install(install_arg).find { |s| s.name == gem }
37
+ Gem.clear_paths
38
+ spec
39
+ end
40
+
41
+ def latest_gem_version(name)
42
+ available_gems = dep_installer.find_gems_with_sources(new_dep(name))
43
+
44
+ spec, source = if available_gems.respond_to?(:last)
45
+ # DependencyInstaller sorts the results such that the last one is
46
+ # always the one it considers best.
47
+ spec_with_source = available_gems.last
48
+ spec_with_source && spec_with_source
49
+ else
50
+ # Rubygems 2.0 returns a Gem::Available set, which is a
51
+ # collection of AvailableSet::Tuple structs
52
+ available_gems.pick_best!
53
+ best_gem = available_gems.set.first
54
+ best_gem && [best_gem.spec, best_gem.source]
55
+ end
56
+
57
+ spec && spec.version && spec.version.to_s
58
+ end
59
+
60
+ def silence_gem_ui
61
+ interaction = Gem::DefaultUserInteraction.ui
62
+ Gem::DefaultUserInteraction.ui = Gem::SilentUI.new
63
+ yield
64
+ ensure
65
+ Gem::DefaultUserInteraction.ui = interaction
66
+ end
67
+
68
+ private
69
+
70
+ def dep_installer
71
+ Gem::DependencyInstaller.new
72
+ end
73
+
74
+ def new_dep(name, version = nil)
75
+ Gem::Dependency.new(name, version)
76
+ end
77
+ end
78
+ end
@@ -24,9 +24,23 @@ require 'busser/runner_plugin'
24
24
  #
25
25
  class Busser::RunnerPlugin::Dummy < Busser::RunnerPlugin::Base
26
26
 
27
+ # Example postinstall block that will be executed after the plugin is
28
+ # installed. All Thor actions, and Busser::Helper methods are available for
29
+ # use.
30
+ #
27
31
  postinstall do
28
- empty_directory("dummy")
29
- create_file("dummy/foobar.txt", "The Dummy Driver.")
32
+ # ensure that dummy_path gets pulled into the chef_apply block closure,
33
+ # otherwise the Chef will not understand dummy_path in its run context
34
+ dummy_path = suite_path("dummy").to_s
35
+
36
+ # expensive operation delegating a directory creation to Chef, but imagine
37
+ # using resources such as package, remote_file, etc.
38
+ chef_apply do
39
+ directory(dummy_path) { recursive true }
40
+ end
41
+
42
+ # create a dummy file
43
+ create_file("#{dummy_path}/foobar.txt", "The Dummy Driver.")
30
44
  end
31
45
 
32
46
  def test
@@ -17,5 +17,5 @@
17
17
  # limitations under the License.
18
18
 
19
19
  module Busser
20
- VERSION = "0.2.0"
20
+ VERSION = "0.3.0"
21
21
  end
@@ -8,17 +8,17 @@ describe Busser::Helpers do
8
8
 
9
9
  describe ".suite_path" do
10
10
 
11
- it "Returns a Pathname" do
11
+ it "returns a Pathname" do
12
12
  suite_path.must_be_kind_of Pathname
13
13
  end
14
14
 
15
15
  describe "with a default root path" do
16
16
 
17
- it "Returns a base path if no suite name is given" do
17
+ it "returns a base path if no suite name is given" do
18
18
  suite_path.to_s.must_equal "/opt/busser/suites"
19
19
  end
20
20
 
21
- it "Returns a suite path given a suite name" do
21
+ it "returns a suite path given a suite name" do
22
22
  suite_path("fuzzy").to_s.must_equal "/opt/busser/suites/fuzzy"
23
23
  end
24
24
  end
@@ -28,15 +28,49 @@ describe Busser::Helpers do
28
28
  before { ENV['_SPEC_BUSSER_ROOT'] = ENV['BUSSER_ROOT'] }
29
29
  after { ENV['BUSSER_ROOT'] = ENV.delete('_SPEC_BUSSER_ROOT') }
30
30
 
31
- it "Returns a base path if no suite name is given" do
31
+ it "returns a base path if no suite name is given" do
32
32
  ENV['BUSSER_ROOT'] = "/path/to/busser"
33
33
  suite_path.to_s.must_equal "/path/to/busser/suites"
34
34
  end
35
35
 
36
- it "Returns a suite path given a suite name" do
36
+ it "returns a suite path given a suite name" do
37
37
  ENV['BUSSER_ROOT'] = "/path/to/busser"
38
38
  suite_path("fuzzy").to_s.must_equal "/path/to/busser/suites/fuzzy"
39
39
  end
40
40
  end
41
41
  end
42
+
43
+ describe ".vendor_path" do
44
+
45
+ it "returns a Pathname" do
46
+ vendor_path.must_be_kind_of Pathname
47
+ end
48
+
49
+ describe "with a default root path" do
50
+
51
+ it "returns a base path if no product name is given" do
52
+ vendor_path.to_s.must_equal "/opt/busser/vendor"
53
+ end
54
+
55
+ it "returns a vendor path given a product name" do
56
+ vendor_path("supreme").to_s.must_equal "/opt/busser/vendor/supreme"
57
+ end
58
+ end
59
+
60
+ describe "with a custom root path" do
61
+
62
+ before { ENV['_SPEC_BUSSER_ROOT'] = ENV['BUSSER_ROOT'] }
63
+ after { ENV['BUSSER_ROOT'] = ENV.delete('_SPEC_BUSSER_ROOT') }
64
+
65
+ it "returns a base path if no product name is given" do
66
+ ENV['BUSSER_ROOT'] = "/path/to/busser"
67
+ vendor_path.to_s.must_equal "/path/to/busser/vendor"
68
+ end
69
+
70
+ it "returns a suite path given a product name" do
71
+ ENV['BUSSER_ROOT'] = "/path/to/busser"
72
+ vendor_path("maximal").to_s.must_equal "/path/to/busser/vendor/maximal"
73
+ end
74
+ end
75
+ end
42
76
  end
@@ -0,0 +1,3 @@
1
+ ## 0.1.0 / Unreleased
2
+
3
+ * Initial release
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
@@ -0,0 +1,41 @@
1
+ # <a name="title"></a> Busser::<%= config[:type_klass_name] %>::<%= config[:klass_name] %>
2
+
3
+ A Busser <%= config[:type] %> plugin for <%= config[:klass_name] %>
4
+
5
+ ## <a name="installation"></a> Installation and Setup
6
+
7
+ Please read the Busser [plugin usage][plugin_usage] page for more details.
8
+
9
+ ## <a name="usage"></a> Usage
10
+
11
+ **TODO:** Write documentation explaining the structure/format of testing files.
12
+
13
+ ## <a name="development"></a> Development
14
+
15
+ * Source hosted at [GitHub][repo]
16
+ * Report issues/questions/feature requests on [GitHub Issues][issues]
17
+
18
+ Pull requests are very welcome! Make sure your patches are well tested.
19
+ Ideally create a topic branch for every separate change you make. For
20
+ example:
21
+
22
+ 1. Fork the repo
23
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
24
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
25
+ 4. Push to the branch (`git push origin my-new-feature`)
26
+ 5. Create new Pull Request
27
+
28
+ ## <a name="authors"></a> Authors
29
+
30
+ Created and maintained by [<%= config[:author] %>][author] (<<%= config[:email] %>>)
31
+
32
+ ## <a name="license"></a> License
33
+
34
+ <%= config[:license_string] %> (see [LICENSE][license])
35
+
36
+
37
+ [author]: https://github.com/enter-github-user
38
+ [issues]: https://github.com/enter-github-user/<%= config[:gem_name] %>/issues
39
+ [license]: https://github.com/enter-github-user/<%= config[:gem_name] %>/blob/master/LICENSE
40
+ [repo]: https://github.com/enter-github-user/<%= config[:gem_name] %>
41
+ [plugin_usage]: http://docs.kitchen-ci.org/busser/plugin-usage
@@ -0,0 +1,31 @@
1
+ require "bundler/gem_tasks"
2
+ require 'cucumber/rake/task'
3
+ require 'cane/rake_task'
4
+ require 'tailor/rake_task'
5
+
6
+ Cucumber::Rake::Task.new(:features) do |t|
7
+ t.cucumber_opts = ['features', '-x', '--format progress']
8
+ end
9
+
10
+ desc "Run all test suites"
11
+ task :test => [:features]
12
+
13
+ desc "Run cane to check quality metrics"
14
+ Cane::RakeTask.new do |cane|
15
+ cane.canefile = './.cane'
16
+ end
17
+
18
+ Tailor::RakeTask.new
19
+
20
+ desc "Display LOC stats"
21
+ task :stats do
22
+ puts "\n## Production Code Stats"
23
+ sh "countloc -r lib"
24
+ puts "\n## Test Code Stats"
25
+ sh "countloc -r features"
26
+ end
27
+
28
+ desc "Run all quality tasks"
29
+ task :quality => [:cane, :tailor, :stats]
30
+
31
+ task :default => [:test, :quality]