diff --git a/lib/puppet/network/formats.rb b/lib/puppet/network/formats.rb index 745a8c0cf..b9605563b 100644 --- a/lib/puppet/network/formats.rb +++ b/lib/puppet/network/formats.rb @@ -1,161 +1,161 @@ require 'puppet/network/format_handler' Puppet::Network::FormatHandler.create_serialized_formats(:msgpack, :weight => 20, :mime => "application/x-msgpack", :required_methods => [:render_method, :intern_method], :intern_method => :from_data_hash) do confine :feature => :msgpack def intern(klass, text) data = MessagePack.unpack(text) return data if data.is_a?(klass) klass.from_data_hash(data) end def intern_multiple(klass, text) MessagePack.unpack(text).collect do |data| klass.from_data_hash(data) end end def render_multiple(instances) instances.to_msgpack end end Puppet::Network::FormatHandler.create_serialized_formats(:yaml) do def intern(klass, text) - data = YAML.load(text, :safe => true, :deserialize_symbols => true) + data = YAML.load(text) data_to_instance(klass, data) end def intern_multiple(klass, text) data = YAML.load(text, :safe => true, :deserialize_symbols => true) unless data.respond_to?(:collect) raise Puppet::Network::FormatHandler::FormatError, "Serialized YAML did not contain a collection of instances when calling intern_multiple" end data.collect do |datum| data_to_instance(klass, datum) end end def data_to_instance(klass, data) return data if data.is_a?(klass) unless data.is_a? Hash raise Puppet::Network::FormatHandler::FormatError, "Serialized YAML did not contain a valid instance of #{klass}" end klass.from_data_hash(data) end def render(instance) instance.to_yaml end # Yaml monkey-patches Array, so this works. def render_multiple(instances) instances.to_yaml end def supported?(klass) true end end 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_serialized_formats(:pson, :weight => 10, :required_methods => [:render_method, :intern_method], :intern_method => :from_data_hash) do 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. # This is required for compatibility with Puppet 3.x def data_to_instance(klass, data) if data.is_a?(Hash) and d = data['data'] data = d end return data if data.is_a?(klass) klass.from_data_hash(data) end end # This is really only ever going to be used for Catalogs. Puppet::Network::FormatHandler.create_serialized_formats(:dot, :required_methods => [:render_method]) Puppet::Network::FormatHandler.create(:console, :mime => 'text/x-console-text', :weight => 0) do def json @json ||= Puppet::Network::FormatHandler.format(:pson) end def render(datum) # String to String return datum if datum.is_a? String return datum if datum.is_a? Numeric # Simple hash to table if datum.is_a? Hash and datum.keys.all? { |x| x.is_a? String or x.is_a? Numeric } output = '' column_a = datum.empty? ? 2 : datum.map{ |k,v| k.to_s.length }.max + 2 datum.sort_by { |k,v| k.to_s } .each do |key, value| output << key.to_s.ljust(column_a) output << json.render(value). chomp.gsub(/\n */) { |x| x + (' ' * column_a) } output << "\n" end return output end # Print one item per line for arrays if datum.is_a? Array output = '' datum.each do |item| output << item.to_s output << "\n" end return output end # ...or pretty-print the inspect outcome. return json.render(datum) end def render_multiple(data) data.collect(&:render).join("\n") end end diff --git a/spec/unit/network/formats_spec.rb b/spec/unit/network/formats_spec.rb index 6b745bd05..714bed40e 100755 --- a/spec/unit/network/formats_spec.rb +++ b/spec/unit/network/formats_spec.rb @@ -1,420 +1,338 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/network/formats' class PsonTest attr_accessor :string def ==(other) string == other.string end def self.from_data_hash(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 msgpack format", :if => Puppet.features.msgpack? do Puppet::Network::FormatHandler.format(:msgpack).should_not be_nil end describe "msgpack", :if => Puppet.features.msgpack? do before do @msgpack = Puppet::Network::FormatHandler.format(:msgpack) end it "should have its mime type set to application/x-msgpack" do @msgpack.mime.should == "application/x-msgpack" end it "should have a weight of 20" do @msgpack.weight.should == 20 end it "should fail when one element does not have a from_data_hash" do expect do @msgpack.intern_multiple(Hash, MessagePack.pack(["foo"])) end.to raise_error(NoMethodError) end it "should be able to serialize a catalog" do cat = Puppet::Resource::Catalog.new('foo', Puppet::Node::Environment.create(:testing, [])) cat.add_resource(Puppet::Resource.new(:file, 'my_file')) catunpack = MessagePack.unpack(cat.to_msgpack) catunpack.should include( "tags"=>[], "name"=>"foo", "version"=>nil, "environment"=>"testing", "edges"=>[], "classes"=>[] ) catunpack["resources"][0].should include( "type"=>"File", "title"=>"my_file", "exported"=>false ) catunpack["resources"][0]["tags"].should include( "file", "my_file" ) end end # it "should not include a yaml format" do # Puppet::Network::FormatHandler.format(:yaml).should be_nil # end # # it "should not include a b64_zlib_yaml format" do # Puppet::Network::FormatHandler.format(:b64_zlib_yaml).should 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 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 deserialize YAML" do @yaml.intern(String, YAML.dump("foo")).should == "foo" end it "should deserialize symbols as strings" do - @yaml.intern(String, YAML.dump(:foo)).should == "foo" + expect { @yaml.intern(String, YAML.dump(:foo))}.to raise_error(Puppet::Network::FormatHandler::FormatError) end it "should load from yaml when deserializing an array" do text = YAML.dump(["foo"]) @yaml.intern_multiple(String, text).should == ["foo"] end it "fails intelligibly instead of calling to_pson with something other than a hash" do expect do @yaml.intern(Puppet::Node, '') end.to raise_error(Puppet::Network::FormatHandler::FormatError, /did not contain a valid instance/) end it "fails intelligibly when intern_multiple is called and yaml doesn't decode to an array" do expect do @yaml.intern_multiple(Puppet::Node, '') end.to raise_error(Puppet::Network::FormatHandler::FormatError, /did not contain a collection/) end it "fails intelligibly instead of calling to_pson with something other than a hash when interning multiple" do expect do @yaml.intern_multiple(Puppet::Node, YAML.dump(["hello"])) end.to raise_error(Puppet::Network::FormatHandler::FormatError, /did not contain a valid instance/) end end -# describe "base64 compressed yaml", :if => Puppet.features.zlib? do -# 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 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 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 round trip data" do -# @yaml.intern(String, @yaml.encode("foo")).should == "foo" -# end -# -# it "should round trip multiple data elements" do -# data = @yaml.render_multiple(["foo", "bar"]) -# @yaml.intern_multiple(String, data).should == ["foo", "bar"] -# end -# -# it "should intern by base64 decoding, uncompressing and safely Yaml loading" do -# input = Base64.encode64(Zlib::Deflate.deflate(YAML.dump("data in"))) -# -# @yaml.intern(String, input).should == "data in" -# end -# -# it "should render by compressing and base64 encoding" do -# output = @yaml.render("foo") -# -# YAML.load(Zlib::Inflate.inflate(Base64.decode64(output))).should == "foo" -# end -# -# describe "when zlib is not installed" do -# before :each do -# Puppet.features.stubs(:zlib?).returns(false) -# end -# -# it "should refuse to encode" do -# expect { -# @yaml.render("foo") -# }.to raise_error(Puppet::Error, /zlib library is not installed/) -# end -# -# it "should refuse to decode" do -# expect { -# @yaml.intern(String, "foo") -# }.to raise_error(Puppet::Error, /zlib library is not installed/) -# end -# -# it "use_zlib? should return false" do -# expect(@yaml).to_not be_use_zlib -# end -# 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 "dot" do before do @dot = Puppet::Network::FormatHandler.format(:dot) end it "should have its mimetype set to text/dot" do @dot.mime.should == "text/dot" 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 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_data_hash to convert the data into an instance" do text = "foo" PSON.expects(:parse).with("foo").returns("type" => "PsonTest", "data" => "foo") PsonTest.expects(:from_data_hash).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_data_hash).never @pson.intern(PsonTest, text).should equal(instance) end it "should intern by calling 'PSON.parse' on the text and then using from_data_hash 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_data_hash).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_data_hash).with("bar").returns "BAR" PsonTest.expects(:from_data_hash).with("baz").returns "BAZ" @pson.intern_multiple(PsonTest, text).should == %w{BAR BAZ} end it "fails intelligibly when given invalid data" do expect do @pson.intern(Puppet::Node, '') end.to raise_error(PSON::ParserError, /source did not contain any PSON/) end end end describe ":console format" do subject { Puppet::Network::FormatHandler.format(:console) } it { should be_an_instance_of Puppet::Network::Format } let :json do Puppet::Network::FormatHandler.format(:pson) end [:intern, :intern_multiple].each do |method| it "should not implement #{method}" do expect { subject.send(method, String, 'blah') }.to raise_error NotImplementedError end end ["hello", 1, 1.0].each do |input| it "should just return a #{input.inspect}" do subject.render(input).should == input end end [[1, 2], ["one"], [{ 1 => 1 }]].each do |input| it "should render #{input.inspect} as one item per line" do subject.render(input).should == input.collect { |item| item.to_s + "\n" }.join('') end end it "should render empty hashes as empty strings" do subject.render({}).should == '' end it "should render a non-trivially-keyed Hash as JSON" do hash = { [1,2] => 3, [2,3] => 5, [3,4] => 7 } subject.render(hash).should == json.render(hash).chomp end it "should render a {String,Numeric}-keyed Hash into a table" do object = Object.new hash = { "one" => 1, "two" => [], "three" => {}, "four" => object, 5 => 5, 6.0 => 6 } # Gotta love ASCII-betical sort order. Hope your objects are better # structured for display than my test one is. --daniel 2011-04-18 subject.render(hash).should == < { "1" => '1' * 40, "2" => '2' * 40, '3' => '3' * 40 }, "text" => { "a" => 'a' * 40, 'b' => 'b' * 40, 'c' => 'c' * 40 } } subject.render(hash).should == <"1111111111111111111111111111111111111111", "2"=>"2222222222222222222222222222222222222222", "3"=>"3333333333333333333333333333333333333333"} text {"a"=>"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "b"=>"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", "c"=>"cccccccccccccccccccccccccccccccccccccccc"} EOT end end end