diff options
27 files changed, 617 insertions, 103 deletions
diff --git a/lib/puppet/application/interface_base.rb b/lib/puppet/application/interface_base.rb index 1dd1f76c2..9a6c8d9ec 100644 --- a/lib/puppet/application/interface_base.rb +++ b/lib/puppet/application/interface_base.rb @@ -68,8 +68,10 @@ class Puppet::Application::InterfaceBase < Puppet::Application @type = self.class.name.to_s.sub(/.+:/, '').downcase.to_sym - @interface = Puppet::Interface.interface(@type).new - @format ||= @interface.class.default_format || :pson + unless @interface = Puppet::Interface.interface(@type) + raise "Could not find interface '#{@type}'" + end + @format ||= @interface.default_format || :pson validate diff --git a/lib/puppet/interface.rb b/lib/puppet/interface.rb index 2e52de43d..901e83af6 100644 --- a/lib/puppet/interface.rb +++ b/lib/puppet/interface.rb @@ -1,60 +1,31 @@ require 'puppet' +require 'puppet/util/autoload' class Puppet::Interface + require 'puppet/interface/action_manager' - class << self - attr_accessor :default_format, :abstract - - # Is this an actual interface, or a base class for others? - def abstract? - abstract - end - - def set_default_format(format) - self.default_format = format.to_sym - end - end - + include Puppet::Interface::ActionManager + extend Puppet::Interface::ActionManager # This is just so we can search for actions. We only use its # list of directories to search. def self.autoloader - require 'puppet/util/autoload' @autoloader ||= Puppet::Util::Autoload.new(:application, "puppet/interface") end - # Declare that this app can take a specific action, and provide - # the code to do so. - def self.action(name, &block) - @actions ||= [] - name = name.to_s.downcase.to_sym - raise "Action #{name} already defined for #{self}" if actions.include?(name) - - @actions << name - - define_method(name, &block) - end - - def self.actions - @actions ||= [] - (if superclass.respond_to?(:actions) - @actions + superclass.actions - else - @actions - end).sort { |a,b| a.to_s <=> b.to_s } - end - # Return an interface by name, loading from disk if necessary. def self.interface(name) - require "puppet/interface/#{name.to_s.downcase}" - self.const_get(name.to_s.capitalize) + @interfaces ||= {} + unless @interfaces[unify_name(name)] + require "puppet/interface/#{unify_name(name)}" + end + @interfaces[unify_name(name)] rescue Exception => detail puts detail.backtrace if Puppet[:trace] $stderr.puts "Unable to find interface '#{name.to_s}': #{detail}." - Kernel::exit(1) end # Try to find actions defined in other files. - def self.load_actions + def self.load_actions(name) path = "puppet/interface/#{name}" autoloader.search_directories.each do |dir| @@ -68,17 +39,43 @@ class Puppet::Interface end end + def self.register_interface(name, instance) + @interfaces ||= {} + @interfaces[unify_name(name)] = instance + const_set(name2const(name), instance) + end + + def self.unload_interface(name) + @interfaces ||= {} + @interfaces.delete(unify_name(name)) + const = name2const(name) + const_get(const) + remove_const(const) + rescue + # nothing - if the constant-getting fails, just return + end + + def self.unify_name(name) + name.to_s.downcase.to_sym + end + + def self.name2const(name) + name.to_s.capitalize + end + + attr_accessor :default_format + + def set_default_format(format) + self.default_format = format.to_sym + end + # Return the interface name. - def self.name + def name @name || self.to_s.sub(/.+::/, '').downcase end attr_accessor :type, :verb, :name, :arguments - def action?(name) - self.class.actions.include?(name.to_sym) - end - # Print the configuration for the current terminus class action :showconfig do |*args| if t = indirection.terminus_class @@ -88,12 +85,22 @@ class Puppet::Interface end end - def initialize(options = {}) + def initialize(name, options = {}, &block) + @name = name + + @default_format = :pson options.each { |opt, val| send(opt.to_s + "=", val) } - Puppet::Util::Log.newdestination :console + # We have to register before loading actions, + # since the actions require the registration + # Use the full class name, so this works with + # subclasses. + Puppet::Interface.register_interface(name, self) - self.class.load_actions - end + Puppet::Interface.load_actions(name) + if block_given? + instance_eval(&block) + end + end end diff --git a/lib/puppet/interface/action_manager.rb b/lib/puppet/interface/action_manager.rb new file mode 100644 index 000000000..27a982929 --- /dev/null +++ b/lib/puppet/interface/action_manager.rb @@ -0,0 +1,32 @@ +module Puppet::Interface::ActionManager + # Declare that this app can take a specific action, and provide + # the code to do so. + def action(name, &block) + @actions ||= [] + name = name.to_s.downcase.to_sym + raise "Action #{name} already defined for #{self}" if action?(name) + + @actions << name + if self.is_a?(Class) + define_method(name, &block) + else + meta_def(name, &block) + end + end + + def actions + @actions ||= [] + result = @actions.dup + + if self.is_a?(Class) and superclass.respond_to?(:actions) + result += superclass.actions + elsif self.class.respond_to?(:actions) + result += self.class.actions + end + result.sort { |a,b| a.to_s <=> b.to_s } + end + + def action?(name) + actions.include?(name.to_sym) + end +end diff --git a/lib/puppet/interface/catalog.rb b/lib/puppet/interface/catalog.rb index 85aa2f37a..b2ed08f92 100644 --- a/lib/puppet/interface/catalog.rb +++ b/lib/puppet/interface/catalog.rb @@ -1,4 +1,4 @@ require 'puppet/interface/indirector' -class Puppet::Interface::Catalog < Puppet::Interface::Indirector +Puppet::Interface::Indirector.new(:catalog) do end diff --git a/lib/puppet/interface/certificate.rb b/lib/puppet/interface/certificate.rb index 48ca2c20f..52ba4e3b8 100644 --- a/lib/puppet/interface/certificate.rb +++ b/lib/puppet/interface/certificate.rb @@ -1,4 +1,4 @@ require 'puppet/interface/indirector' -class Puppet::Interface::Certificate < Puppet::Interface::Indirector +Puppet::Interface::Indirector.new(:certificate) do end diff --git a/lib/puppet/interface/certificate_request.rb b/lib/puppet/interface/certificate_request.rb index 29dc73b9a..77b485f8e 100644 --- a/lib/puppet/interface/certificate_request.rb +++ b/lib/puppet/interface/certificate_request.rb @@ -1,4 +1,4 @@ require 'puppet/interface/indirector' -class Puppet::Interface::Certificate_request < Puppet::Interface::Indirector +Puppet::Interface::Indirector.new(:certificate_request) do end diff --git a/lib/puppet/interface/certificate_revocation_list.rb b/lib/puppet/interface/certificate_revocation_list.rb index 144d5ef61..ee1e6a8c4 100644 --- a/lib/puppet/interface/certificate_revocation_list.rb +++ b/lib/puppet/interface/certificate_revocation_list.rb @@ -1,4 +1,4 @@ require 'puppet/interface/indirector' -class Puppet::Interface::Certificate_revocation_list < Puppet::Interface::Indirector +Puppet::Interface::Indirector.new(:certificate_revocation_list) do end diff --git a/lib/puppet/interface/facts.rb b/lib/puppet/interface/facts.rb index 42ba1fb81..7b269e0d0 100644 --- a/lib/puppet/interface/facts.rb +++ b/lib/puppet/interface/facts.rb @@ -1,6 +1,7 @@ require 'puppet/interface/indirector' +require 'puppet/node/facts' -class Puppet::Interface::Facts < Puppet::Interface::Indirector +Puppet::Interface::Indirector.new(:facts) do set_default_format :yaml # Upload our facts to the server diff --git a/lib/puppet/interface/file.rb b/lib/puppet/interface/file.rb index 98a869153..9060c4042 100644 --- a/lib/puppet/interface/file.rb +++ b/lib/puppet/interface/file.rb @@ -1,7 +1,5 @@ require 'puppet/interface/indirector' -class Puppet::Interface::File < Puppet::Interface::Indirector - def self.indirection_name - :file_bucket_file - end +class Puppet::Interface::Indirector.new(:file) do + set_indirection_name :file_bucket_file end diff --git a/lib/puppet/interface/indirector.rb b/lib/puppet/interface/indirector.rb index 507826b91..feb356d85 100644 --- a/lib/puppet/interface/indirector.rb +++ b/lib/puppet/interface/indirector.rb @@ -2,27 +2,14 @@ require 'puppet' require 'puppet/interface' class Puppet::Interface::Indirector < Puppet::Interface - - # This is just a base class. - @abstract = true - - # Here's your opportunity to override the indirection name. By default - # it will be the same name as the interface. - def self.indirection_name - name.to_sym + def self.indirections + Puppet::Indirector::Indirection.instances.collect { |t| t.to_s }.sort end - # Return an indirection associated with an interface, if one exists - # One usually does. - def self.indirection - unless @indirection - Puppet.info("Could not find terminus for #{indirection_name}") unless @indirection = Puppet::Indirector::Indirection.instance(indirection_name) - end - @indirection + def self.terminus_classes(indirection) + Puppet::Indirector::Terminus.terminus_classes(indirection.to_sym).collect { |t| t.to_s }.sort end - attr_accessor :from, :indirection - action :destroy do |name, *args| call_indirection_method(:destroy, name, *args) end @@ -39,16 +26,25 @@ class Puppet::Interface::Indirector < Puppet::Interface call_indirection_method(:search, name, *args) end - def indirection - self.class.indirection - end + attr_accessor :from - def initialize(options = {}) - options.each { |opt, val| send(opt.to_s + "=", val) } + def indirection_name + @indirection_name || name.to_sym + end - Puppet::Util::Log.newdestination :console + # Here's your opportunity to override the indirection name. By default + # it will be the same name as the interface. + def set_indirection_name(name) + @indirection_name = name + end - self.class.load_actions + # Return an indirection associated with an interface, if one exists + # One usually does. + def indirection + unless @indirection + Puppet.info("Could not find terminus for #{indirection_name}") unless @indirection = Puppet::Indirector::Indirection.instance(indirection_name) + end + @indirection end def set_terminus(from) @@ -64,21 +60,9 @@ class Puppet::Interface::Indirector < Puppet::Interface result = indirection.send(method, name, *args) rescue => detail puts detail.backtrace if Puppet[:trace] - raise "Could not call #{method} on #{type}: #{detail}" - end - - unless result - raise "Could not #{method} #{indirection.name} for #{name}" + raise "Could not call '#{method}' on '#{indirection_name}': #{detail}" end result end - - def indirections - Puppet::Indirector::Indirection.instances.collect { |t| t.to_s }.sort - end - - def terminus_classes(indirection) - Puppet::Indirector::Terminus.terminus_classes(indirection).collect { |t| t.to_s }.sort - end end diff --git a/lib/puppet/interface/inventory.rb b/lib/puppet/interface/inventory.rb index 16b216b8b..9b597c6ae 100644 --- a/lib/puppet/interface/inventory.rb +++ b/lib/puppet/interface/inventory.rb @@ -1,4 +1,4 @@ require 'puppet/interface/indirector' -class Puppet::Interface::Inventory < Puppet::Interface::Indirector +Puppet::Interface::Indirector.new(:inventory) do end diff --git a/lib/puppet/interface/key.rb b/lib/puppet/interface/key.rb index 17b661da1..9343891d0 100644 --- a/lib/puppet/interface/key.rb +++ b/lib/puppet/interface/key.rb @@ -1,4 +1,4 @@ require 'puppet/interface/indirector' -class Puppet::Interface::Key < Puppet::Interface::Indirector +Puppet::Interface::Indirector.new(:key) do end diff --git a/lib/puppet/interface/node.rb b/lib/puppet/interface/node.rb index 5d9efa932..7d7362d8b 100644 --- a/lib/puppet/interface/node.rb +++ b/lib/puppet/interface/node.rb @@ -1,4 +1,4 @@ require 'puppet/interface/indirector' -class Puppet::Interface::Node < Puppet::Interface::Indirector +Puppet::Interface::Indirector.new(:node) do end diff --git a/lib/puppet/interface/report.rb b/lib/puppet/interface/report.rb index fd6f45f16..e7b916527 100644 --- a/lib/puppet/interface/report.rb +++ b/lib/puppet/interface/report.rb @@ -1,4 +1,4 @@ require 'puppet/interface/indirector' -class Puppet::Interface::Report < Puppet::Interface::Indirector +Puppet::Interface::Indirector.new(:report) do end diff --git a/lib/puppet/interface/resource.rb b/lib/puppet/interface/resource.rb index deed0a533..65f2dec7a 100644 --- a/lib/puppet/interface/resource.rb +++ b/lib/puppet/interface/resource.rb @@ -1,4 +1,4 @@ require 'puppet/interface/indirector' -class Puppet::Interface::Resource < Puppet::Interface::Indirector +Puppet::Interface::Indirector.new(:resource) do end diff --git a/lib/puppet/interface/resource_type.rb b/lib/puppet/interface/resource_type.rb index 6892926f0..bf16652a8 100644 --- a/lib/puppet/interface/resource_type.rb +++ b/lib/puppet/interface/resource_type.rb @@ -1,4 +1,4 @@ require 'puppet/interface/indirector' -class Puppet::Interface::Resource_type < Puppet::Interface::Indirector +Puppet::Interface::Indirector.new(:resource_type) do end diff --git a/lib/puppet/interface/status.rb b/lib/puppet/interface/status.rb index 86ccab6e1..1a1d349d1 100644 --- a/lib/puppet/interface/status.rb +++ b/lib/puppet/interface/status.rb @@ -1,4 +1,4 @@ require 'puppet/interface/indirector' -class Puppet::Interface::Status < Puppet::Interface::Indirector +Puppet::Interface::Indirector.new(:status) do end diff --git a/spec/README.markdown b/spec/README.markdown new file mode 100644 index 000000000..286d3417d --- /dev/null +++ b/spec/README.markdown @@ -0,0 +1,7 @@ +Specs +===== + +The Puppet project uses RSpec for testing. + +For more information on RSpec, see http://rspec.info/ + diff --git a/spec/spec.opts b/spec/spec.opts new file mode 100644 index 000000000..91cd6427e --- /dev/null +++ b/spec/spec.opts @@ -0,0 +1,6 @@ +--format +s +--colour +--loadby +mtime +--backtrace diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 000000000..242ef0a40 --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,17 @@ +require 'pathname' +dir = Pathname.new(__FILE__).parent +$LOAD_PATH.unshift(dir, dir + 'lib', dir + '../lib') + +require 'mocha' +require 'puppet' +require 'rspec' + +RSpec.configure do |config| + config.mock_with :mocha +end + +# We need this because the RAL uses 'should' as a method. This +# allows us the same behaviour but with a different method name. +class Object + alias :must :should +end diff --git a/spec/unit/interface/action_manager_spec.rb b/spec/unit/interface/action_manager_spec.rb new file mode 100644 index 000000000..b71aecaa2 --- /dev/null +++ b/spec/unit/interface/action_manager_spec.rb @@ -0,0 +1,142 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') + +# This is entirely an internal class for Interface, so we have to load it instead of our class. +require 'puppet/interface' + +class ActionManagerTester + include Puppet::Interface::ActionManager +end + +describe Puppet::Interface::ActionManager do + before do + @tester = ActionManagerTester.new + end + + describe "when included in a class" do + it "should be able to define an action" do + @tester.action(:foo) { "something "} + end + + it "should be able to list defined actions" do + @tester.action(:foo) { "something" } + @tester.action(:bar) { "something" } + + @tester.actions.should be_include(:bar) + @tester.actions.should be_include(:foo) + end + + it "should be able to indicate when an action is defined" do + @tester.action(:foo) { "something" } + @tester.should be_action(:foo) + end + end + + describe "when used to extend a class" do + before do + @tester = Class.new + @tester.extend(Puppet::Interface::ActionManager) + end + + it "should be able to define an action" do + @tester.action(:foo) { "something "} + end + + it "should be able to list defined actions" do + @tester.action(:foo) { "something" } + @tester.action(:bar) { "something" } + + @tester.actions.should be_include(:bar) + @tester.actions.should be_include(:foo) + end + + it "should be able to indicate when an action is defined" do + @tester.action(:foo) { "something" } + @tester.should be_action(:foo) + end + end + + describe "when used both at the class and instance level" do + before do + @klass = Class.new do + include Puppet::Interface::ActionManager + extend Puppet::Interface::ActionManager + end + @instance = @klass.new + end + + it "should be able to define an action at the class level" do + @klass.action(:foo) { "something "} + end + + it "should create an instance method when an action is defined at the class level" do + @klass.action(:foo) { "something" } + @instance.foo.should == "something" + end + + it "should be able to define an action at the instance level" do + @instance.action(:foo) { "something "} + end + + it "should create an instance method when an action is defined at the instance level" do + @instance.action(:foo) { "something" } + @instance.foo.should == "something" + end + + it "should be able to list actions defined at the class level" do + @klass.action(:foo) { "something" } + @klass.action(:bar) { "something" } + + @klass.actions.should be_include(:bar) + @klass.actions.should be_include(:foo) + end + + it "should be able to list actions defined at the instance level" do + @instance.action(:foo) { "something" } + @instance.action(:bar) { "something" } + + @instance.actions.should be_include(:bar) + @instance.actions.should be_include(:foo) + end + + it "should be able to list actions defined at both instance and class level" do + @klass.action(:foo) { "something" } + @instance.action(:bar) { "something" } + + @instance.actions.should be_include(:bar) + @instance.actions.should be_include(:foo) + end + + it "should be able to indicate when an action is defined at the class level" do + @klass.action(:foo) { "something" } + @instance.should be_action(:foo) + end + + it "should be able to indicate when an action is defined at the instance level" do + @klass.action(:foo) { "something" } + @instance.should be_action(:foo) + end + + it "should list actions defined in superclasses" do + @subclass = Class.new(@klass) + @instance = @subclass.new + + @klass.action(:parent) { "a" } + @subclass.action(:sub) { "a" } + @instance.action(:instance) { "a" } + + @instance.should be_action(:parent) + @instance.should be_action(:sub) + @instance.should be_action(:instance) + end + + it "should create an instance method when an action is defined in a superclass" do + @subclass = Class.new(@klass) + @instance = @subclass.new + + @klass.action(:foo) { "something" } + @instance.foo.should == "something" + end + end +end diff --git a/spec/unit/interface/facts_spec.rb b/spec/unit/interface/facts_spec.rb new file mode 100644 index 000000000..03d6410f9 --- /dev/null +++ b/spec/unit/interface/facts_spec.rb @@ -0,0 +1,26 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') +require 'puppet/interface/facts' + +describe Puppet::Interface.interface(:facts) do + before do + @interface = Puppet::Interface.interface(:facts) + end + + it "should define an 'upload' fact" do + @interface.should be_action(:upload) + end + + it "should set its default format to :yaml" do + @interface.default_format.should == :yaml + end + + describe "when uploading" do + it "should set the terminus_class to :facter" + + it "should set the cach_eclass to :rest" + + it "should find the current certname" + end +end diff --git a/spec/unit/interface/indirector_spec.rb b/spec/unit/interface/indirector_spec.rb new file mode 100644 index 000000000..1e5ee3068 --- /dev/null +++ b/spec/unit/interface/indirector_spec.rb @@ -0,0 +1,61 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') +require 'puppet/interface/indirector' + +describe Puppet::Interface::Indirector do + before do + @instance = Puppet::Interface::Indirector.new(:test) + + @indirection = stub 'indirection', :name => :stub_indirection + + @instance.stubs(:indirection).returns @indirection + end + + after do + Puppet::Interface.unload_interface(:test) + end + + it "should be a subclass of Interface" do + Puppet::Interface::Indirector.superclass.should equal(Puppet::Interface) + end + + it "should be able to return a list of indirections" do + Puppet::Interface::Indirector.indirections.should be_include("catalog") + end + + it "should be able to return a list of terminuses for a given indirection" do + Puppet::Interface::Indirector.terminus_classes(:catalog).should be_include("compiler") + end + + describe "as an instance" do + after { Puppet::Interface.unload_interface(:catalog) } + + it "should be able to determine its indirection" do + # Loading actions here an get, um, complicated + Puppet::Interface.stubs(:load_actions) + Puppet::Interface::Indirector.new(:catalog).indirection.should equal(Puppet::Resource::Catalog.indirection) + end + end + + [:find, :search, :save, :destroy].each do |method| + it "should define a '#{method}' action" do + Puppet::Interface::Indirector.should be_action(method) + end + + it "should just call the indirection method when the '#{method}' action is invoked" do + @instance.indirection.expects(method).with(:test, "myargs") + @instance.send(method, :test, "myargs") + end + + it "should be able to override its indirection name" do + @instance.set_indirection_name :foo + @instance.indirection_name.should == :foo + end + + it "should be able to set its terminus class" do + @instance.indirection.expects(:terminus_class=).with(:myterm) + @instance.set_terminus(:myterm) + end + end +end diff --git a/spec/unit/interface_spec.rb b/spec/unit/interface_spec.rb new file mode 100644 index 000000000..4fe797b6b --- /dev/null +++ b/spec/unit/interface_spec.rb @@ -0,0 +1,99 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../spec_helper.rb') +require 'puppet/interface' + +describe Puppet::Interface do + after do + Puppet::Interface.unload_interface(:me) + end + + describe "at initialization" do + it "should require a name" do + Puppet::Interface.new(:me).name.should == :me + end + + it "should register itself" do + Puppet::Interface.expects(:register_interface).with { |name, inst| name == :me and inst.is_a?(Puppet::Interface) } + Puppet::Interface.new(:me) + end + + it "should load actions" do + Puppet::Interface.expects(:load_actions).with(:me) + Puppet::Interface.new(:me) + end + + it "should instance-eval any provided block" do + face = Puppet::Interface.new(:me) do + action(:something) { "foo" } + end + + face.should be_action(:something) + end + end + + it "should allow overriding of the default format" do + face = Puppet::Interface.new(:me) + face.set_default_format :foo + face.default_format.should == :foo + end + + it "should default to :pson for its format" do + Puppet::Interface.new(:me).default_format.should == :pson + end + + it "should create a class-level autoloader" do + Puppet::Interface.autoloader.should be_instance_of(Puppet::Util::Autoload) + end + + it "should define a class-level 'showconfig' action" do + Puppet::Interface.should be_action(:showconfig) + end + + it "should set any provided options" do + Puppet::Interface.new(:me, :verb => "foo").verb.should == "foo" + end + + it "should be able to register and return interfaces" do + $stderr.stubs(:puts) + face = Puppet::Interface.new(:me) + Puppet::Interface.unload_interface(:me) # to remove from the initial registration + Puppet::Interface.register_interface(:me, face) + Puppet::Interface.interface(:me).should equal(face) + end + + it "should create an associated constant when registering an interface" do + $stderr.stubs(:puts) + face = Puppet::Interface.new(:me) + Puppet::Interface.unload_interface(:me) # to remove from the initial registration + Puppet::Interface.register_interface(:me, face) + Puppet::Interface::Me.should equal(face) + end + + it "should be able to unload interfaces" do + $stderr.stubs(:puts) + face = Puppet::Interface.new(:me) + Puppet::Interface.unload_interface(:me) + Puppet::Interface.interface(:me).should be_nil + end + + it "should remove the associated constant when an interface is unregistered" do + $stderr.stubs(:puts) + face = Puppet::Interface.new(:me) + Puppet::Interface.unload_interface(:me) + lambda { Puppet::Interface.const_get("Me") }.should raise_error(NameError) + end + + it "should try to require interfaces that are not known" do + Puppet::Interface.expects(:require).with "puppet/interface/foo" + Puppet::Interface.interface(:foo) + end + + it "should not fail when requiring an interface fails" do + $stderr.stubs(:puts) + Puppet::Interface.expects(:require).with("puppet/interface/foo").raises LoadError + lambda { Puppet::Interface.interface(:foo) }.should_not raise_error + end + + it "should be able to load all actions in all search paths" +end diff --git a/spec/unit/puppet/provider/README.markdown b/spec/unit/puppet/provider/README.markdown new file mode 100644 index 000000000..702585021 --- /dev/null +++ b/spec/unit/puppet/provider/README.markdown @@ -0,0 +1,4 @@ +Provider Specs +============== + +Define specs for your providers under this directory. diff --git a/spec/unit/puppet/type/README.markdown b/spec/unit/puppet/type/README.markdown new file mode 100644 index 000000000..1ee19ac84 --- /dev/null +++ b/spec/unit/puppet/type/README.markdown @@ -0,0 +1,4 @@ +Resource Type Specs +=================== + +Define specs for your resource types in this directory. diff --git a/spec/watchr.rb b/spec/watchr.rb new file mode 100644 index 000000000..476176fd3 --- /dev/null +++ b/spec/watchr.rb @@ -0,0 +1,124 @@ +ENV["WATCHR"] = "1" +ENV['AUTOTEST'] = 'true' + +def run_comp(cmd) + puts cmd + results = [] + old_sync = $stdout.sync + $stdout.sync = true + line = [] + begin + open("| #{cmd}", "r") do |f| + until f.eof? do + c = f.getc + putc c + line << c + if c == ?\n + results << if RUBY_VERSION >= "1.9" then + line.join + else + line.pack "c*" + end + line.clear + end + end + end + ensure + $stdout.sync = old_sync + end + results.join +end + +def clear + #system("clear") +end + +def growl(message, status) + # Strip the color codes + message.gsub!(/\[\d+m/, '') + + growlnotify = `which growlnotify`.chomp + return if growlnotify.empty? + title = "Watchr Test Results" + image = status == :pass ? "autotest/images/pass.png" : "autotest/images/fail.png" + options = "-w -n Watchr --image '#{File.expand_path(image)}' -m '#{message}' '#{title}'" + system %(#{growlnotify} #{options} &) +end + +def file2specs(file) + %w{spec/unit spec/integration}.collect { |d| + file.sub('lib/puppet', d).sub('.rb', '_spec.rb') + }.find_all { |f| + FileTest.exist?(f) + } +end + +def run_spec(command) + clear + result = run_comp(command).split("\n").last + status = result.include?('0 failures') ? :pass : :fail + growl result, status +end + +def run_spec_files(files) + files = Array(files) + return if files.empty? + opts = File.readlines('spec/spec.opts').collect { |l| l.chomp }.join(" ") + begin + run_spec("rspec #{files.join(' ')}") + rescue => detail + puts detail.backtrace + warn "Failed to run #{files.join(', ')}: #{detail}" + end +end + +def run_suite + files = files("unit") + files("integration") + run_spec("rspec #{files.join(' ')}") +end + +def files(dir) + require 'find' + + result = [] + Find.find(File.join("spec", dir)) do |path| + result << path if path =~ /\.rb/ + end + + result +end + +watch('spec/spec_helper.rb') { run_suite } +watch(%r{^spec/(unit|integration)/.*\.rb$}) { |md| run_spec_files(md[0]) } +watch(%r{^lib/puppet/(.*)\.rb$}) { |md| + run_spec_files(file2specs(md[0])) +} +watch(%r{^spec/lib/spec.*}) { |md| run_suite } +watch(%r{^spec/lib/monkey_patches/.*}) { |md| run_suite } + +# Ctrl-\ +Signal.trap 'QUIT' do + puts " --- Running all tests ---\n\n" + run_suite +end + +@interrupted = false + +# Ctrl-C +Signal.trap 'INT' do + if @interrupted + @wants_to_quit = true + abort("\n") + else + puts "Interrupt a second time to quit; wait for rerun of tests" + @interrupted = true + Kernel.sleep 1.5 + # raise Interrupt, nil # let the run loop catch it + begin + run_suite + rescue => detail + puts detail.backtrace + puts "Could not run suite: #{detail}" + end + end +end |
