diff --git a/lib/puppet/network/format.rb b/lib/puppet/network/format.rb index d78124221..7c3cc7bd8 100644 --- a/lib/puppet/network/format.rb +++ b/lib/puppet/network/format.rb @@ -1,113 +1,114 @@ require 'puppet/provider' require 'puppet/provider/confiner' # A simple class for modeling encoding formats for moving # instances around the network. class Puppet::Network::Format include Puppet::Provider::Confiner attr_reader :name, :mime - attr_accessor :intern_method, :render_method, :intern_multiple_method, :render_multiple_method, :weight, :required_methods + attr_accessor :intern_method, :render_method, :intern_multiple_method, :render_multiple_method, :weight, :required_methods, :extension def init_attribute(name, default) if value = @options[name] @options.delete(name) else value = default end self.send(name.to_s + "=", value) end def initialize(name, options = {}, &block) @name = name.to_s.downcase.intern @options = options # This must be done early the values can be used to set required_methods define_method_names() method_list = { :intern_method => "from_%s" % name, :intern_multiple_method => "from_multiple_%s" % name, :render_multiple_method => "to_multiple_%s" % name, :render_method => "to_%s" % name } init_attribute(:mime, "text/%s" % name) init_attribute(:weight, 5) init_attribute(:required_methods, method_list.keys) + init_attribute(:extension, name.to_s) method_list.each do |method, value| init_attribute(method, value) end unless @options.empty? raise ArgumentError, "Unsupported option(s) %s" % @options.keys end @options = nil instance_eval(&block) if block_given? end def intern(klass, text) return klass.send(intern_method, text) if klass.respond_to?(intern_method) raise NotImplementedError, "%s does not respond to %s; can not intern instances from %s" % [klass, intern_method, mime] end def intern_multiple(klass, text) return klass.send(intern_multiple_method, text) if klass.respond_to?(intern_multiple_method) raise NotImplementedError, "%s does not respond to %s; can not intern multiple instances from %s" % [klass, intern_multiple_method, mime] end def mime=(mime) @mime = mime.to_s.downcase end def render(instance) return instance.send(render_method) if instance.respond_to?(render_method) raise NotImplementedError, "%s does not respond to %s; can not render instances to %s" % [instance.class, render_method, mime] end def render_multiple(instances) # This method implicitly assumes that all instances are of the same type. return instances[0].class.send(render_multiple_method, instances) if instances[0].class.respond_to?(render_multiple_method) raise NotImplementedError, "%s does not respond to %s; can not intern multiple instances to %s" % [instances[0].class, render_multiple_method, mime] end def required_methods_present?(klass) [:intern_method, :intern_multiple_method, :render_multiple_method].each do |name| return false unless required_method_present?(name, klass, :class) end return false unless required_method_present?(:render_method, klass, :instance) return true end def supported?(klass) suitable? and required_methods_present?(klass) end def to_s "Puppet::Network::Format[%s]" % name end private def define_method_names @intern_method = "from_%s" % name @render_method = "to_%s" % name @intern_multiple_method = "from_multiple_%s" % name @render_multiple_method = "to_multiple_%s" % name end def required_method_present?(name, klass, type) return true unless required_methods.include?(name) method = send(name) return klass.respond_to?(method) if type == :class return klass.instance_methods.include?(method) end end diff --git a/lib/puppet/network/format_handler.rb b/lib/puppet/network/format_handler.rb index ea8cf35de..a637f8b89 100644 --- a/lib/puppet/network/format_handler.rb +++ b/lib/puppet/network/format_handler.rb @@ -1,170 +1,177 @@ require 'yaml' require 'puppet/network' require 'puppet/network/format' module Puppet::Network::FormatHandler class FormatError < Puppet::Error; end class FormatProtector attr_reader :format def protect(method, args) begin Puppet::Network::FormatHandler.format(format).send(method, *args) rescue => details direction = method.to_s.include?("intern") ? "from" : "to" error = FormatError.new("Could not %s %s %s: %s" % [method, direction, format, details]) error.set_backtrace(details.backtrace) raise error end end def initialize(format) @format = format end [:intern, :intern_multiple, :render, :render_multiple, :mime].each do |method| define_method(method) do |*args| protect(method, args) end end end @formats = {} def self.create(*args, &block) instance = Puppet::Network::Format.new(*args) instance.instance_eval(&block) if block_given? @formats[instance.name] = instance instance end def self.extended(klass) klass.extend(ClassMethods) # LAK:NOTE This won't work in 1.9 ('send' won't be able to send # private methods, but I don't know how else to do it. klass.send(:include, InstanceMethods) end def self.format(name) @formats[name.to_s.downcase.intern] end + def self.format_by_extension(ext) + @formats.each do |name, format| + return format if format.extension == ext + end + return nil + end + # Provide a list of all formats. def self.formats @formats.keys end # Return a format capable of handling the provided mime type. def self.mime(mimetype) mimetype = mimetype.to_s.downcase @formats.values.find { |format| format.mime == mimetype } end # Use a delegator to make sure any exceptions generated by our formats are # handled intelligently. def self.protected_format(name) name = format_to_canonical_name(name) @format_protectors ||= {} @format_protectors[name] ||= FormatProtector.new(name) @format_protectors[name] end # Return a format name given: # * a format name # * a mime-type # * a format instance def self.format_to_canonical_name(format) case format when Puppet::Network::Format out = format when %r{\w+/\w+} out = mime(format) else out = format(format) end raise ArgumentError, "No format match the given format name or mime-type (%s)" % format if out.nil? out.name end module ClassMethods def format_handler Puppet::Network::FormatHandler end def convert_from(format, data) format_handler.protected_format(format).intern(self, data) end def convert_from_multiple(format, data) format_handler.protected_format(format).intern_multiple(self, data) end def render_multiple(format, instances) format_handler.protected_format(format).render_multiple(instances) end def default_format supported_formats[0] end def support_format?(name) Puppet::Network::FormatHandler.format(name).supported?(self) end def supported_formats result = format_handler.formats.collect { |f| format_handler.format(f) }.find_all { |f| f.supported?(self) }.collect { |f| f.name }.sort do |a, b| # It's an inverse sort -- higher weight formats go first. format_handler.format(b).weight <=> format_handler.format(a).weight end result = put_preferred_format_first(result) Puppet.debug "#{friendly_name} supports formats: #{result.map{ |f| f.to_s }.sort.join(' ')}; using #{result.first}" result end private def friendly_name if self.respond_to? :indirection indirection.name else self end end def put_preferred_format_first(list) preferred_format = Puppet.settings[:preferred_serialization_format].to_sym if list.include?(preferred_format) list.delete(preferred_format) list.unshift(preferred_format) else Puppet.warning "Value of 'preferred_serialization_format' (#{preferred_format}) is invalid for #{friendly_name}, using default (#{list.first})" end list end end module InstanceMethods def render(format = nil) format ||= self.class.default_format Puppet::Network::FormatHandler.protected_format(format).render(self) end def mime(format = nil) format ||= self.class.default_format Puppet::Network::FormatHandler.protected_format(format).mime end def support_format?(name) self.class.support_format?(name) end end end require 'puppet/network/formats' diff --git a/lib/puppet/network/formats.rb b/lib/puppet/network/formats.rb index a98dcbcc5..62569d4b1 100644 --- a/lib/puppet/network/formats.rb +++ b/lib/puppet/network/formats.rb @@ -1,188 +1,188 @@ require 'puppet/network/format_handler' Puppet::Network::FormatHandler.create(:yaml, :mime => "text/yaml") do # Yaml doesn't need the class name; it's serialized. def intern(klass, text) YAML.load(text) end # Yaml doesn't need the class name; it's serialized. def intern_multiple(klass, text) YAML.load(text) end def render(instance) yaml = instance.to_yaml yaml = fixup(yaml) unless yaml.nil? yaml end # Yaml monkey-patches Array, so this works. def render_multiple(instances) yaml = instances.to_yaml yaml = fixup(yaml) unless yaml.nil? yaml end # Everything's supported unless you're on 1.8.1 def supported?(klass) RUBY_VERSION != '1.8.1' end # fixup invalid yaml as per: # http://redmine.ruby-lang.org/issues/show/1331 def fixup(yaml) yaml.gsub!(/((?:&id\d+\s+)?!ruby\/object:.*?)\s*\?/) { "? #{$1}" } yaml end end # This is a "special" format which is used for the moment only when sending facts # as REST GET parameters (see Puppet::Configurer::FactHandler). # This format combines a yaml serialization, then zlib compression and base64 encoding. Puppet::Network::FormatHandler.create(:b64_zlib_yaml, :mime => "text/b64_zlib_yaml") do require 'base64' def use_zlib? Puppet.features.zlib? && Puppet[:zlib] end def requiring_zlib if use_zlib? yield else raise Puppet::Error, "the zlib library is not installed or is disabled." end end def intern(klass, text) decode(text) end def intern_multiple(klass, text) decode(text) end def render(instance) yaml = instance.to_yaml yaml = encode(fixup(yaml)) unless yaml.nil? yaml end def render_multiple(instances) yaml = instances.to_yaml yaml = encode(fixup(yaml)) unless yaml.nil? yaml end # Because of yaml issue in ruby 1.8.1... def supported?(klass) RUBY_VERSION != '1.8.1' and use_zlib? end # fixup invalid yaml as per: # http://redmine.ruby-lang.org/issues/show/1331 def fixup(yaml) yaml.gsub!(/((?:&id\d+\s+)?!ruby\/object:.*?)\s*\?/) { "? #{$1}" } yaml end def encode(text) requiring_zlib do Base64.encode64(Zlib::Deflate.deflate(text, Zlib::BEST_COMPRESSION)) end end def decode(yaml) requiring_zlib do YAML.load(Zlib::Inflate.inflate(Base64.decode64(yaml))) end end end Puppet::Network::FormatHandler.create(:marshal, :mime => "text/marshal") do # Marshal doesn't need the class name; it's serialized. def intern(klass, text) Marshal.load(text) end # Marshal doesn't need the class name; it's serialized. def intern_multiple(klass, text) Marshal.load(text) end def render(instance) Marshal.dump(instance) end # Marshal monkey-patches Array, so this works. def render_multiple(instances) Marshal.dump(instances) end # Everything's supported def supported?(klass) true end end -Puppet::Network::FormatHandler.create(:s, :mime => "text/plain") +Puppet::Network::FormatHandler.create(:s, :mime => "text/plain", :extension => "txt") # A very low-weight format so it'll never get chosen automatically. Puppet::Network::FormatHandler.create(:raw, :mime => "application/x-raw", :weight => 1) do def intern_multiple(klass, text) raise NotImplementedError end def render_multiple(instances) raise NotImplementedError end # LAK:NOTE The format system isn't currently flexible enough to handle # what I need to support raw formats just for individual instances (rather # than both individual and collections), but we don't yet have enough data # to make a "correct" design. # So, we hack it so it works for singular but fail if someone tries it # on plurals. def supported?(klass) true end end Puppet::Network::FormatHandler.create(:pson, :mime => "text/pson", :weight => 10, :required_methods => [:render_method, :intern_method]) do confine :true => Puppet.features.pson? def intern(klass, text) data_to_instance(klass, PSON.parse(text)) end def intern_multiple(klass, text) PSON.parse(text).collect do |data| data_to_instance(klass, data) end end # PSON monkey-patches Array, so this works. def render_multiple(instances) instances.to_pson end # If they pass class information, we want to ignore it. By default, # we'll include class information but we won't rely on it - we don't # want class names to be required because we then can't change our # internal class names, which is bad. def data_to_instance(klass, data) if data.is_a?(Hash) and d = data['data'] data = d end if data.is_a?(klass) return data end klass.from_pson(data) end end diff --git a/spec/unit/network/format.rb b/spec/unit/network/format.rb index e5a6b599c..73530b9d7 100755 --- a/spec/unit/network/format.rb +++ b/spec/unit/network/format.rb @@ -1,191 +1,198 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/network/format' # A class with all of the necessary # hooks. class FormatRenderer def self.to_multiple_my_format(list) end def self.from_multiple_my_format(text) end def self.from_my_format(text) end def to_my_format end end describe Puppet::Network::Format do describe "when initializing" do it "should require a name" do lambda { Puppet::Network::Format.new }.should raise_error(ArgumentError) end it "should be able to provide its name" do Puppet::Network::Format.new(:my_format).name.should == :my_format end it "should always convert its name to a downcased symbol" do Puppet::Network::Format.new(:My_Format).name.should == :my_format end it "should be able to set its downcased mime type at initialization" do format = Puppet::Network::Format.new(:my_format, :mime => "Foo/Bar") format.mime.should == "foo/bar" end it "should default to text plus the name of the format as the mime type" do Puppet::Network::Format.new(:my_format).mime.should == "text/my_format" end it "should fail if unsupported options are provided" do lambda { Puppet::Network::Format.new(:my_format, :foo => "bar") }.should raise_error(ArgumentError) end end describe "instances" do before do @format = Puppet::Network::Format.new(:my_format) end it "should support being confined" do @format.should respond_to(:confine) end it "should not be considered suitable if confinement conditions are not met" do @format.confine :true => false @format.should_not be_suitable end it "should be able to determine if a class is supported" do @format.should respond_to(:supported?) end it "should consider a class to be supported if it has the individual and multiple methods for rendering and interning" do @format.should be_supported(FormatRenderer) end it "should default to its required methods being the individual and multiple methods for rendering and interning" do Puppet::Network::Format.new(:foo).required_methods.sort { |a,b| a.to_s <=> b.to_s }.should == [:intern_method, :intern_multiple_method, :render_multiple_method, :render_method].sort { |a,b| a.to_s <=> b.to_s } end it "should consider a class supported if the provided class has all required methods present" do format = Puppet::Network::Format.new(:foo) [:intern_method, :intern_multiple_method, :render_multiple_method, :render_method].each do |method| format.expects(:required_method_present?).with { |name, klass, type| name == method and klass == String }.returns true end format.should be_required_methods_present(String) end it "should consider a class not supported if any required methods are missing from the provided class" do format = Puppet::Network::Format.new(:foo) format.stubs(:required_method_present?).returns true format.expects(:required_method_present?).with { |name, *args| name == :intern_method }.returns false format.should_not be_required_methods_present(String) end it "should be able to specify the methods required for support" do Puppet::Network::Format.new(:foo, :required_methods => [:render_method, :intern_method]).required_methods.should == [:render_method, :intern_method] end it "should only test for required methods if specific methods are specified as required" do format = Puppet::Network::Format.new(:foo, :required_methods => [:intern_method]) format.expects(:required_method_present?).with { |name, klass, type| name == :intern_method } format.required_methods_present?(String) end it "should not consider a class supported unless the format is suitable" do @format.expects(:suitable?).returns false @format.should_not be_supported(FormatRenderer) end it "should always downcase mimetypes" do @format.mime = "Foo/Bar" @format.mime.should == "foo/bar" end it "should support having a weight" do @format.should respond_to(:weight) end it "should default to a weight of of 5" do @format.weight.should == 5 end it "should be able to override its weight at initialization" do Puppet::Network::Format.new(:foo, :weight => 1).weight.should == 1 end + it "should default to its extension being equal to its name" do + Puppet::Network::Format.new(:foo).extension.should == "foo" + end + + it "should support overriding the extension" do + Puppet::Network::Format.new(:foo, :extension => "bar").extension.should == "bar" + end [:intern_method, :intern_multiple_method, :render_multiple_method, :render_method].each do |method| it "should allow assignment of the #{method}" do Puppet::Network::Format.new(:foo, method => :foo).send(method).should == :foo end end end describe "when converting between instances and formatted text" do before do @format = Puppet::Network::Format.new(:my_format) @instance = FormatRenderer.new end it "should have a method for rendering a single instance" do @format.should respond_to(:render) end it "should have a method for rendering multiple instances" do @format.should respond_to(:render_multiple) end it "should have a method for interning text" do @format.should respond_to(:intern) end it "should have a method for interning text into multiple instances" do @format.should respond_to(:intern_multiple) end it "should return the results of calling the instance-specific render method if the method is present" do @instance.expects(:to_my_format).returns "foo" @format.render(@instance).should == "foo" end it "should return the results of calling the class-specific render_multiple method if the method is present" do @instance.class.expects(:to_multiple_my_format).returns ["foo"] @format.render_multiple([@instance]).should == ["foo"] end it "should return the results of calling the class-specific intern method if the method is present" do FormatRenderer.expects(:from_my_format).with("foo").returns @instance @format.intern(FormatRenderer, "foo").should equal(@instance) end it "should return the results of calling the class-specific intern_multiple method if the method is present" do FormatRenderer.expects(:from_multiple_my_format).with("foo").returns [@instance] @format.intern_multiple(FormatRenderer, "foo").should == [@instance] end it "should fail if asked to render and the instance does not respond to 'to_'" do lambda { @format.render("foo") }.should raise_error(NotImplementedError) end it "should fail if asked to intern and the class does not respond to 'from_'" do lambda { @format.intern(String, "foo") }.should raise_error(NotImplementedError) end it "should fail if asked to intern multiple and the class does not respond to 'from_multiple_'" do lambda { @format.intern_multiple(String, "foo") }.should raise_error(NotImplementedError) end it "should fail if asked to render multiple and the instance does not respond to 'to_multiple_'" do lambda { @format.render_multiple(["foo", "bar"]) }.should raise_error(NotImplementedError) end end end diff --git a/spec/unit/network/format_handler.rb b/spec/unit/network/format_handler.rb index 110effe09..f379e5cce 100755 --- a/spec/unit/network/format_handler.rb +++ b/spec/unit/network/format_handler.rb @@ -1,326 +1,335 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/network/format_handler' class FormatTester extend Puppet::Network::FormatHandler end describe Puppet::Network::FormatHandler do after do formats = Puppet::Network::FormatHandler.instance_variable_get("@formats") formats.each do |name, format| formats.delete(name) unless format.is_a?(Puppet::Network::Format) end end it "should be able to list supported formats" do FormatTester.should respond_to(:supported_formats) end it "should include all supported formats" do one = stub 'supported', :supported? => true, :name => :one, :weight => 1 two = stub 'supported', :supported? => false, :name => :two, :weight => 1 three = stub 'supported', :supported? => true, :name => :three, :weight => 1 four = stub 'supported', :supported? => false, :name => :four, :weight => 1 Puppet::Network::FormatHandler.stubs(:formats).returns [:one, :two, :three, :four] Puppet::Network::FormatHandler.stubs(:format).with(:one).returns one Puppet::Network::FormatHandler.stubs(:format).with(:two).returns two Puppet::Network::FormatHandler.stubs(:format).with(:three).returns three Puppet::Network::FormatHandler.stubs(:format).with(:four).returns four result = FormatTester.supported_formats result.length.should == 2 result.should be_include(:one) result.should be_include(:three) end it "should return the supported formats in decreasing order of weight" do one = stub 'supported', :supported? => true, :name => :one, :weight => 1 two = stub 'supported', :supported? => true, :name => :two, :weight => 6 three = stub 'supported', :supported? => true, :name => :three, :weight => 2 four = stub 'supported', :supported? => true, :name => :four, :weight => 8 Puppet::Network::FormatHandler.stubs(:formats).returns [:one, :two, :three, :four] Puppet::Network::FormatHandler.stubs(:format).with(:one).returns one Puppet::Network::FormatHandler.stubs(:format).with(:two).returns two Puppet::Network::FormatHandler.stubs(:format).with(:three).returns three Puppet::Network::FormatHandler.stubs(:format).with(:four).returns four FormatTester.supported_formats.should == [:four, :two, :three, :one] end describe "with a preferred serialization format setting" do before do one = stub 'supported', :supported? => true, :name => :one, :weight => 1 two = stub 'supported', :supported? => true, :name => :two, :weight => 6 Puppet::Network::FormatHandler.stubs(:formats).returns [:one, :two] Puppet::Network::FormatHandler.stubs(:format).with(:one).returns one Puppet::Network::FormatHandler.stubs(:format).with(:two).returns two end describe "that is supported" do before do Puppet.settings.expects(:value).with(:preferred_serialization_format).returns :one end it "should return the preferred serialization format first" do FormatTester.supported_formats.should == [:one, :two] end end describe "that is not supported" do before do Puppet.settings.expects(:value).with(:preferred_serialization_format).returns :unsupported end it "should still return the default format first" do FormatTester.supported_formats.should == [:two, :one] end it "should log a warning" do Puppet.expects(:warning) FormatTester.supported_formats end end end it "should return the first format as the default format" do FormatTester.expects(:supported_formats).returns [:one, :two] FormatTester.default_format.should == :one end it "should be able to use a protected format for better logging on errors" do Puppet::Network::FormatHandler.should respond_to(:protected_format) end it "should delegate all methods from the informative format to the specified format" do format = mock 'format' format.stubs(:name).returns(:myformat) Puppet::Network::FormatHandler.expects(:format).twice.with(:myformat).returns format format.expects(:render).with("foo").returns "yay" Puppet::Network::FormatHandler.protected_format(:myformat).render("foo").should == "yay" end it "should provide better logging if a failure is encountered when delegating from the informative format to the real format" do format = mock 'format' format.stubs(:name).returns(:myformat) Puppet::Network::FormatHandler.expects(:format).twice.with(:myformat).returns format format.expects(:render).with("foo").raises "foo" lambda { Puppet::Network::FormatHandler.protected_format(:myformat).render("foo") }.should raise_error(Puppet::Network::FormatHandler::FormatError) end it "should raise an error if we couldn't find a format by name or mime-type" do Puppet::Network::FormatHandler.stubs(:format).with(:myformat).returns nil lambda { Puppet::Network::FormatHandler.protected_format(:myformat) }.should raise_error end describe "when using formats" do before do @format = mock 'format' @format.stubs(:supported?).returns true @format.stubs(:name).returns :my_format Puppet::Network::FormatHandler.stubs(:format).with(:my_format).returns @format Puppet::Network::FormatHandler.stubs(:mime).with("text/myformat").returns @format Puppet::Network::Format.stubs(:===).returns false Puppet::Network::Format.stubs(:===).with(@format).returns true end it "should be able to test whether a format is supported" do FormatTester.should respond_to(:support_format?) end it "should use the Format to determine whether a given format is supported" do @format.expects(:supported?).with(FormatTester) FormatTester.support_format?(:my_format) end it "should be able to convert from a given format" do FormatTester.should respond_to(:convert_from) end it "should call the format-specific converter when asked to convert from a given format" do @format.expects(:intern).with(FormatTester, "mydata") FormatTester.convert_from(:my_format, "mydata") end it "should call the format-specific converter when asked to convert from a given format by mime-type" do @format.expects(:intern).with(FormatTester, "mydata") FormatTester.convert_from("text/myformat", "mydata") end it "should call the format-specific converter when asked to convert from a given format by format instance" do @format.expects(:intern).with(FormatTester, "mydata") FormatTester.convert_from(@format, "mydata") end it "should raise a FormatError when an exception is encountered when converting from a format" do @format.expects(:intern).with(FormatTester, "mydata").raises "foo" lambda { FormatTester.convert_from(:my_format, "mydata") }.should raise_error(Puppet::Network::FormatHandler::FormatError) end it "should be able to use a specific hook for converting into multiple instances" do @format.expects(:intern_multiple).with(FormatTester, "mydata") FormatTester.convert_from_multiple(:my_format, "mydata") end it "should raise a FormatError when an exception is encountered when converting multiple items from a format" do @format.expects(:intern_multiple).with(FormatTester, "mydata").raises "foo" lambda { FormatTester.convert_from_multiple(:my_format, "mydata") }.should raise_error(Puppet::Network::FormatHandler::FormatError) end it "should be able to use a specific hook for rendering multiple instances" do @format.expects(:render_multiple).with("mydata") FormatTester.render_multiple(:my_format, "mydata") end it "should raise a FormatError when an exception is encountered when rendering multiple items into a format" do @format.expects(:render_multiple).with("mydata").raises "foo" lambda { FormatTester.render_multiple(:my_format, "mydata") }.should raise_error(Puppet::Network::FormatHandler::FormatError) end end describe "when managing formats" do it "should have a method for defining a new format" do Puppet::Network::FormatHandler.should respond_to(:create) end it "should create a format instance when asked" do format = stub 'format', :name => :foo Puppet::Network::Format.expects(:new).with(:foo).returns format Puppet::Network::FormatHandler.create(:foo) end it "should instance_eval any block provided when creating a format" do format = stub 'format', :name => :instance_eval format.expects(:yayness) Puppet::Network::Format.expects(:new).returns format Puppet::Network::FormatHandler.create(:instance_eval) do yayness end end it "should be able to retrieve a format by name" do format = Puppet::Network::FormatHandler.create(:by_name) Puppet::Network::FormatHandler.format(:by_name).should equal(format) end + it "should be able to retrieve a format by extension" do + format = Puppet::Network::FormatHandler.create(:by_extension, :extension => "foo") + Puppet::Network::FormatHandler.format_by_extension("foo").should equal(format) + end + + it "should return nil if asked to return a format by an unknown extension" do + Puppet::Network::FormatHandler.format_by_extension("yayness").should be_nil + end + it "should be able to retrieve formats by name irrespective of case and class" do format = Puppet::Network::FormatHandler.create(:by_name) Puppet::Network::FormatHandler.format(:By_Name).should equal(format) end it "should be able to retrieve a format by mime type" do format = Puppet::Network::FormatHandler.create(:by_name, :mime => "foo/bar") Puppet::Network::FormatHandler.mime("foo/bar").should equal(format) end it "should be able to retrieve a format by mime type irrespective of case" do format = Puppet::Network::FormatHandler.create(:by_name, :mime => "foo/bar") Puppet::Network::FormatHandler.mime("Foo/Bar").should equal(format) end it "should be able to return all formats" do one = stub 'one', :name => :one two = stub 'two', :name => :two Puppet::Network::Format.expects(:new).with(:one).returns(one) Puppet::Network::Format.expects(:new).with(:two).returns(two) Puppet::Network::FormatHandler.create(:one) Puppet::Network::FormatHandler.create(:two) list = Puppet::Network::FormatHandler.formats list.should be_include(:one) list.should be_include(:two) end end describe "when an instance" do it "should be able to test whether a format is supported" do FormatTester.new.should respond_to(:support_format?) end it "should be able to convert to a given format" do FormatTester.new.should respond_to(:render) end it "should be able to get a format mime-type" do FormatTester.new.should respond_to(:mime) end it "should raise a FormatError when a rendering error is encountered" do format = stub 'rendering format', :supported? => true, :name => :foo Puppet::Network::FormatHandler.stubs(:format).with(:foo).returns format tester = FormatTester.new format.expects(:render).with(tester).raises "eh" lambda { tester.render(:foo) }.should raise_error(Puppet::Network::FormatHandler::FormatError) end it "should call the format-specific converter when asked to convert to a given format" do format = stub 'rendering format', :supported? => true, :name => :foo Puppet::Network::FormatHandler.stubs(:format).with(:foo).returns format tester = FormatTester.new format.expects(:render).with(tester).returns "foo" tester.render(:foo).should == "foo" end it "should call the format-specific converter when asked to convert to a given format by mime-type" do format = stub 'rendering format', :supported? => true, :name => :foo Puppet::Network::FormatHandler.stubs(:mime).with("text/foo").returns format Puppet::Network::FormatHandler.stubs(:format).with(:foo).returns format tester = FormatTester.new format.expects(:render).with(tester).returns "foo" tester.render("text/foo").should == "foo" end it "should call the format converter when asked to convert to a given format instance" do format = stub 'rendering format', :supported? => true, :name => :foo Puppet::Network::Format.stubs(:===).with(format).returns(true) Puppet::Network::FormatHandler.stubs(:format).with(:foo).returns format tester = FormatTester.new format.expects(:render).with(tester).returns "foo" tester.render(format).should == "foo" end it "should render to the default format if no format is provided when rendering" do format = stub 'rendering format', :supported? => true, :name => :foo Puppet::Network::FormatHandler.stubs(:format).with(:foo).returns format FormatTester.expects(:default_format).returns :foo tester = FormatTester.new format.expects(:render).with(tester) tester.render end it "should call the format-specific converter when asked for the mime-type of a given format" do format = stub 'rendering format', :supported? => true, :name => :foo Puppet::Network::FormatHandler.stubs(:format).with(:foo).returns format tester = FormatTester.new format.expects(:mime).returns "text/foo" tester.mime(:foo).should == "text/foo" end it "should return the default format mime-type if no format is provided" do format = stub 'rendering format', :supported? => true, :name => :foo Puppet::Network::FormatHandler.stubs(:format).with(:foo).returns format FormatTester.expects(:default_format).returns :foo tester = FormatTester.new format.expects(:mime).returns "text/foo" tester.mime.should == "text/foo" end end end diff --git a/spec/unit/network/formats.rb b/spec/unit/network/formats.rb index a241306a2..7b0c0adba 100755 --- a/spec/unit/network/formats.rb +++ b/spec/unit/network/formats.rb @@ -1,365 +1,369 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/network/formats' class PsonTest attr_accessor :string def ==(other) string == other.string end def self.from_pson(data) new(data) end def initialize(string) @string = string end def to_pson(*args) { 'type' => self.class.name, 'data' => @string }.to_pson(*args) end end describe "Puppet Network Format" do it "should include a yaml format" do Puppet::Network::FormatHandler.format(:yaml).should_not be_nil end describe "yaml" do before do @yaml = Puppet::Network::FormatHandler.format(:yaml) end it "should have its mime type set to text/yaml" do @yaml.mime.should == "text/yaml" end it "should be supported on Strings" do @yaml.should be_supported(String) end it "should render by calling 'to_yaml' on the instance" do instance = mock 'instance' instance.expects(:to_yaml).returns "foo" @yaml.render(instance).should == "foo" end it "should fixup generated yaml on render" do instance = mock 'instance', :to_yaml => "foo" @yaml.expects(:fixup).with("foo").returns "bar" @yaml.render(instance).should == "bar" end it "should render multiple instances by calling 'to_yaml' on the array" do instances = [mock('instance')] instances.expects(:to_yaml).returns "foo" @yaml.render_multiple(instances).should == "foo" end it "should fixup generated yaml on render" do instances = [mock('instance')] instances.stubs(:to_yaml).returns "foo" @yaml.expects(:fixup).with("foo").returns "bar" @yaml.render(instances).should == "bar" end it "should intern by calling 'YAML.load'" do text = "foo" YAML.expects(:load).with("foo").returns "bar" @yaml.intern(String, text).should == "bar" end it "should intern multiples by calling 'YAML.load'" do text = "foo" YAML.expects(:load).with("foo").returns "bar" @yaml.intern_multiple(String, text).should == "bar" end it "should fixup incorrect yaml to correct" do @yaml.fixup("&id004 !ruby/object:Puppet::Relationship ?").should == "? &id004 !ruby/object:Puppet::Relationship" end end describe "base64 compressed yaml" do yaml = Puppet::Network::FormatHandler.format(:b64_zlib_yaml) confine "We must have zlib" => Puppet.features.zlib? before do @yaml = Puppet::Network::FormatHandler.format(:b64_zlib_yaml) end it "should have its mime type set to text/b64_zlib_yaml" do @yaml.mime.should == "text/b64_zlib_yaml" end it "should render by calling 'to_yaml' on the instance" do instance = mock 'instance' instance.expects(:to_yaml).returns "foo" @yaml.render(instance) end it "should fixup generated yaml on render" do instance = mock 'instance', :to_yaml => "foo" @yaml.expects(:fixup).with("foo").returns "bar" @yaml.render(instance) end it "should encode generated yaml on render" do instance = mock 'instance', :to_yaml => "foo" @yaml.expects(:encode).with("foo").returns "bar" @yaml.render(instance).should == "bar" end it "should render multiple instances by calling 'to_yaml' on the array" do instances = [mock('instance')] instances.expects(:to_yaml).returns "foo" @yaml.render_multiple(instances) end it "should fixup generated yaml on render" do instances = [mock('instance')] instances.stubs(:to_yaml).returns "foo" @yaml.expects(:fixup).with("foo").returns "bar" @yaml.render(instances) end it "should encode generated yaml on render" do instances = [mock('instance')] instances.stubs(:to_yaml).returns "foo" @yaml.expects(:encode).with("foo").returns "bar" @yaml.render(instances).should == "bar" end it "should intern by calling decode" do text = "foo" @yaml.expects(:decode).with("foo").returns "bar" @yaml.intern(String, text).should == "bar" end it "should intern multiples by calling 'decode'" do text = "foo" @yaml.expects(:decode).with("foo").returns "bar" @yaml.intern_multiple(String, text).should == "bar" end it "should decode by base64 decoding, uncompressing and Yaml loading" do Base64.expects(:decode64).with("zorg").returns "foo" Zlib::Inflate.expects(:inflate).with("foo").returns "baz" YAML.expects(:load).with("baz").returns "bar" @yaml.decode("zorg").should == "bar" end it "should encode by compressing and base64 encoding" do Zlib::Deflate.expects(:deflate).with("foo", Zlib::BEST_COMPRESSION).returns "bar" Base64.expects(:encode64).with("bar").returns "baz" @yaml.encode("foo").should == "baz" end it "should fixup incorrect yaml to correct" do @yaml.fixup("&id004 !ruby/object:Puppet::Relationship ?").should == "? &id004 !ruby/object:Puppet::Relationship" end describe "when zlib is disabled" do before do Puppet[:zlib] = false end it "use_zlib? should return false" do @yaml.use_zlib?.should == false end it "should refuse to encode" do lambda{ @yaml.encode("foo") }.should raise_error end it "should refuse to decode" do lambda{ @yaml.decode("foo") }.should raise_error end end describe "when zlib is not installed" do it "use_zlib? should return false" do Puppet[:zlib] = true Puppet.features.expects(:zlib?).returns(false) @yaml.use_zlib?.should == false end end end it "should include a marshal format" do Puppet::Network::FormatHandler.format(:marshal).should_not be_nil end describe "marshal" do before do @marshal = Puppet::Network::FormatHandler.format(:marshal) end it "should have its mime type set to text/marshal" do Puppet::Network::FormatHandler.format(:marshal).mime.should == "text/marshal" end it "should be supported on Strings" do @marshal.should be_supported(String) end it "should render by calling 'Marshal.dump' on the instance" do instance = mock 'instance' Marshal.expects(:dump).with(instance).returns "foo" @marshal.render(instance).should == "foo" end it "should render multiple instances by calling 'to_marshal' on the array" do instances = [mock('instance')] Marshal.expects(:dump).with(instances).returns "foo" @marshal.render_multiple(instances).should == "foo" end it "should intern by calling 'Marshal.load'" do text = "foo" Marshal.expects(:load).with("foo").returns "bar" @marshal.intern(String, text).should == "bar" end it "should intern multiples by calling 'Marshal.load'" do text = "foo" Marshal.expects(:load).with("foo").returns "bar" @marshal.intern_multiple(String, text).should == "bar" end end describe "plaintext" do before do @text = Puppet::Network::FormatHandler.format(:s) end it "should have its mimetype set to text/plain" do @text.mime.should == "text/plain" end + + it "should use 'txt' as its extension" do + @text.extension.should == "txt" + end end describe Puppet::Network::FormatHandler.format(:raw) do before do @format = Puppet::Network::FormatHandler.format(:raw) end it "should exist" do @format.should_not be_nil end it "should have its mimetype set to application/x-raw" do @format.mime.should == "application/x-raw" end it "should always be supported" do @format.should be_supported(String) end it "should fail if its multiple_render method is used" do lambda { @format.render_multiple("foo") }.should raise_error(NotImplementedError) end it "should fail if its multiple_intern method is used" do lambda { @format.intern_multiple(String, "foo") }.should raise_error(NotImplementedError) end it "should have a weight of 1" do @format.weight.should == 1 end end it "should include a pson format" do Puppet::Network::FormatHandler.format(:pson).should_not be_nil end describe "pson" do confine "Missing 'pson' library" => Puppet.features.pson? before do @pson = Puppet::Network::FormatHandler.format(:pson) end it "should have its mime type set to text/pson" do Puppet::Network::FormatHandler.format(:pson).mime.should == "text/pson" end it "should require the :render_method" do Puppet::Network::FormatHandler.format(:pson).required_methods.should be_include(:render_method) end it "should require the :intern_method" do Puppet::Network::FormatHandler.format(:pson).required_methods.should be_include(:intern_method) end it "should have a weight of 10" do @pson.weight.should == 10 end describe "when supported" do it "should render by calling 'to_pson' on the instance" do instance = PsonTest.new("foo") instance.expects(:to_pson).returns "foo" @pson.render(instance).should == "foo" end it "should render multiple instances by calling 'to_pson' on the array" do instances = [mock('instance')] instances.expects(:to_pson).returns "foo" @pson.render_multiple(instances).should == "foo" end it "should intern by calling 'PSON.parse' on the text and then using from_pson to convert the data into an instance" do text = "foo" PSON.expects(:parse).with("foo").returns("type" => "PsonTest", "data" => "foo") PsonTest.expects(:from_pson).with("foo").returns "parsed_pson" @pson.intern(PsonTest, text).should == "parsed_pson" end it "should not render twice if 'PSON.parse' creates the appropriate instance" do text = "foo" instance = PsonTest.new("foo") PSON.expects(:parse).with("foo").returns(instance) PsonTest.expects(:from_pson).never @pson.intern(PsonTest, text).should equal(instance) end it "should intern by calling 'PSON.parse' on the text and then using from_pson to convert the actual into an instance if the pson has no class/data separation" do text = "foo" PSON.expects(:parse).with("foo").returns("foo") PsonTest.expects(:from_pson).with("foo").returns "parsed_pson" @pson.intern(PsonTest, text).should == "parsed_pson" end it "should intern multiples by parsing the text and using 'class.intern' on each resulting data structure" do text = "foo" PSON.expects(:parse).with("foo").returns ["bar", "baz"] PsonTest.expects(:from_pson).with("bar").returns "BAR" PsonTest.expects(:from_pson).with("baz").returns "BAZ" @pson.intern_multiple(PsonTest, text).should == %w{BAR BAZ} end end end end