busser 0.2.0 → 0.3.0

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