diff --git a/lib/puppet/resource/status.rb b/lib/puppet/resource/status.rb index dea8c105d..d514a183d 100644 --- a/lib/puppet/resource/status.rb +++ b/lib/puppet/resource/status.rb @@ -1,79 +1,79 @@ module Puppet class Resource class Status include Puppet::Util::Tagging include Puppet::Util::Logging attr_accessor :resource, :node, :file, :line, :current_values, :status, :evaluation_time STATES = [:skipped, :failed, :failed_to_restart, :restarted, :changed, :out_of_sync, :scheduled] attr_accessor *STATES attr_reader :source_description, :default_log_level, :time, :resource attr_reader :change_count, :out_of_sync_count, :resource_type, :title YAML_ATTRIBUTES = %w{@resource @file @line @evaluation_time @change_count @out_of_sync_count @tags @time @events @out_of_sync @changed @resource_type @title @skipped @failed} # Provide a boolean method for each of the states. STATES.each do |attr| define_method("#{attr}?") do !! send(attr) end end def <<(event) add_event(event) self end def add_event(event) @events << event if event.status == 'failure' self.failed = true elsif event.status == 'success' @change_count += 1 @changed = true end if event.status != 'audit' @out_of_sync_count += 1 @out_of_sync = true end end def events @events end def initialize(resource) @source_description = resource.path @resource = resource.to_s @change_count = 0 @out_of_sync_count = 0 @changed = false @out_of_sync = false @skipped = false @failed = false [:file, :line].each do |attr| send(attr.to_s + "=", resource.send(attr)) end tag(*resource.tags) @time = Time.now @events = [] @resource_type = resource.type.to_s.capitalize @title = resource.title end def to_yaml_properties - (YAML_ATTRIBUTES & instance_variables).sort + YAML_ATTRIBUTES & instance_variables end private def log_source source_description end end end end diff --git a/lib/puppet/transaction/event.rb b/lib/puppet/transaction/event.rb index d3f25b71c..5adcc792e 100644 --- a/lib/puppet/transaction/event.rb +++ b/lib/puppet/transaction/event.rb @@ -1,66 +1,66 @@ require 'puppet/transaction' require 'puppet/util/tagging' require 'puppet/util/logging' # A simple struct for storing what happens on the system. class Puppet::Transaction::Event include Puppet::Util::Tagging include Puppet::Util::Logging ATTRIBUTES = [:name, :resource, :property, :previous_value, :desired_value, :historical_value, :status, :message, :file, :line, :source_description, :audited] YAML_ATTRIBUTES = %w{@audited @property @previous_value @desired_value @historical_value @message @name @status @time} attr_accessor *ATTRIBUTES attr_writer :tags attr_accessor :time attr_reader :default_log_level EVENT_STATUSES = %w{noop success failure audit} def initialize(options = {}) @audited = false options.each { |attr, value| send(attr.to_s + "=", value) } @time = Time.now end def property=(prop) @property = prop.to_s end def resource=(res) if res.respond_to?(:[]) and level = res[:loglevel] @default_log_level = level end @resource = res.to_s end def send_log super(log_level, message) end def status=(value) raise ArgumentError, "Event status can only be #{EVENT_STATUSES.join(', ')}" unless EVENT_STATUSES.include?(value) @status = value end def to_s message end def to_yaml_properties - (YAML_ATTRIBUTES.map {|ya| ya.to_s} & instance_variables.map{|iv| iv.to_s}).sort + YAML_ATTRIBUTES & instance_variables end private # If it's a failure, use 'err', else use either the resource's log level (if available) # or 'notice'. def log_level status == "failure" ? :err : (@default_log_level || :notice) end # Used by the Logging module def log_source source_description || property || resource end end diff --git a/lib/puppet/transaction/report.rb b/lib/puppet/transaction/report.rb index bed1975f6..184aa0f1a 100644 --- a/lib/puppet/transaction/report.rb +++ b/lib/puppet/transaction/report.rb @@ -1,200 +1,200 @@ require 'puppet' require 'puppet/indirector' # A class for reporting what happens on each client. Reports consist of # two types of data: Logs and Metrics. Logs are the output that each # change produces, and Metrics are all of the numerical data involved # in the transaction. class Puppet::Transaction::Report extend Puppet::Indirector indirects :report, :terminus_class => :processor attr_accessor :configuration_version, :host, :environment attr_reader :resource_statuses, :logs, :metrics, :time, :kind, :status # This is necessary since Marshall doesn't know how to # dump hash with default proc (see below @records) def self.default_format :yaml end def <<(msg) @logs << msg self end def add_times(name, value) @external_times[name] = value end def add_metric(name, hash) metric = Puppet::Util::Metric.new(name) hash.each do |name, value| metric.newvalue(name, value) end @metrics[metric.name] = metric metric end def add_resource_status(status) @resource_statuses[status.resource] = status end def compute_status(resource_metrics, change_metric) if (resource_metrics["failed"] || 0) > 0 'failed' elsif change_metric > 0 'changed' else 'unchanged' end end def prune_internal_data resource_statuses.delete_if {|name,res| res.resource_type == 'Whit'} end def finalize_report prune_internal_data resource_metrics = add_metric(:resources, calculate_resource_metrics) add_metric(:time, calculate_time_metrics) change_metric = calculate_change_metric add_metric(:changes, {"total" => change_metric}) add_metric(:events, calculate_event_metrics) @status = compute_status(resource_metrics, change_metric) end def initialize(kind, configuration_version=nil, environment=nil) @metrics = {} @logs = [] @resource_statuses = {} @external_times ||= {} @host = Puppet[:node_name_value] @time = Time.now @kind = kind @report_format = 3 @puppet_version = Puppet.version @configuration_version = configuration_version @environment = environment @status = 'failed' # assume failed until the report is finalized end def name host end # Provide a human readable textual summary of this report. def summary report = raw_summary ret = "" report.keys.sort { |a,b| a.to_s <=> b.to_s }.each do |key| ret += "#{Puppet::Util::Metric.labelize(key)}:\n" report[key].keys.sort { |a,b| # sort by label if a == :total 1 elsif b == :total -1 else report[key][a].to_s <=> report[key][b].to_s end }.each do |label| value = report[key][label] next if value == 0 value = "%0.2f" % value if value.is_a?(Float) ret += " %15s %s\n" % [Puppet::Util::Metric.labelize(label) + ":", value] end end ret end # Provide a raw hash summary of this report. def raw_summary report = { "version" => { "config" => configuration_version, "puppet" => Puppet.version } } @metrics.each do |name, metric| key = metric.name.to_s report[key] = {} metric.values.each do |name, label, value| report[key][name.to_s] = value end report[key]["total"] = 0 unless key == "time" or report[key].include?("total") end (report["time"] ||= {})["last_run"] = Time.now.tv_sec report end # Based on the contents of this report's metrics, compute a single number # that represents the report. The resulting number is a bitmask where # individual bits represent the presence of different metrics. def exit_status status = 0 status |= 2 if @metrics["changes"]["total"] > 0 status |= 4 if @metrics["resources"]["failed"] > 0 status end def to_yaml_properties - (instance_variables - ["@external_times"]).sort + (instance_variables - ["@external_times"]) end private def calculate_change_metric resource_statuses.map { |name, status| status.change_count || 0 }.inject(0) { |a,b| a+b } end def calculate_event_metrics metrics = Hash.new(0) %w{total failure success}.each { |m| metrics[m] = 0 } resource_statuses.each do |name, status| metrics["total"] += status.events.length status.events.each do |event| metrics[event.status] += 1 end end metrics end def calculate_resource_metrics metrics = {} metrics["total"] = resource_statuses.length # force every resource key in the report to be present # even if no resources is in this given state Puppet::Resource::Status::STATES.each do |state| metrics[state.to_s] = 0 end resource_statuses.each do |name, status| Puppet::Resource::Status::STATES.each do |state| metrics[state.to_s] += 1 if status.send(state) end end metrics end def calculate_time_metrics metrics = Hash.new(0) resource_statuses.each do |name, status| type = Puppet::Resource.new(name).type metrics[type.to_s.downcase] += status.evaluation_time if status.evaluation_time end @external_times.each do |name, value| metrics[name.to_s.downcase] = value end metrics["total"] = metrics.values.inject(0) { |a,b| a+b } metrics end end diff --git a/lib/puppet/util/zaml.rb b/lib/puppet/util/zaml.rb index e26e112c6..72f593f4f 100644 --- a/lib/puppet/util/zaml.rb +++ b/lib/puppet/util/zaml.rb @@ -1,379 +1,379 @@ # encoding: UTF-8 # # The above encoding line is a magic comment to set the default source encoding # of this file for the Ruby interpreter. It must be on the first or second # line of the file if an interpreter is in use. In Ruby 1.9 and later, the # source encoding determines the encoding of String and Regexp objects created # from this source file. This explicit encoding is important becuase otherwise # Ruby will pick an encoding based on LANG or LC_CTYPE environment variables. # These may be different from site to site so it's important for us to # establish a consistent behavior. For more information on M17n please see: # http://links.puppetlabs.com/understanding_m17n # ZAML -- A partial replacement for YAML, writen with speed and code clarity # in mind. ZAML fixes one YAML bug (loading Exceptions) and provides # a replacement for YAML.dump unimaginatively called ZAML.dump, # which is faster on all known cases and an order of magnitude faster # with complex structures. # # http://github.com/hallettj/zaml # # ## License (from upstream) # # Copyright (c) 2008-2009 ZAML contributers # # This program is dual-licensed under the GNU General Public License # version 3 or later and under the Apache License, version 2.0. # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by the # Free Software Foundation, either version 3 of the License, or (at your # option) any later version; or under the terms of the Apache License, # Version 2.0. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License and the Apache License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see # . # # You may obtain a copy of the Apache License at # . require 'yaml' class ZAML VERSION = "0.1.3" # # Class Methods # def self.dump(stuff, where='') z = new stuff.to_zaml(z) where << z.to_s end # # Instance Methods # def initialize @result = [] @indent = nil @structured_key_prefix = nil @previously_emitted_object = {} @next_free_label_number = 0 emit('--- ') end def nested(tail=' ') old_indent = @indent @indent = "#{@indent || "\n"}#{tail}" yield @indent = old_indent end class Label # # YAML only wants objects in the datastream once; if the same object # occurs more than once, we need to emit a label ("&idxxx") on the # first occurrence and then emit a back reference (*idxxx") on any # subsequent occurrence(s). # # To accomplish this we keeps a hash (by object id) of the labels of # the things we serialize as we begin to serialize them. The labels # initially serialize as an empty string (since most objects are only # going to be be encountered once), but can be changed to a valid # (by assigning it a number) the first time it is subsequently used, # if it ever is. Note that we need to do the label setup BEFORE we # start to serialize the object so that circular structures (in # which we will encounter a reference to the object as we serialize # it can be handled). # attr_accessor :this_label_number def initialize(obj,indent) @indent = indent @this_label_number = nil @obj = obj # prevent garbage collection so that object id isn't reused end def to_s @this_label_number ? ('&id%03d%s' % [@this_label_number, @indent]) : '' end def reference @reference ||= '*id%03d' % @this_label_number end end def label_for(obj) @previously_emitted_object[obj.object_id] end def new_label_for(obj) label = Label.new(obj,(Hash === obj || Array === obj) ? "#{@indent || "\n"} " : ' ') @previously_emitted_object[obj.object_id] = label label end def first_time_only(obj) if label = label_for(obj) label.this_label_number ||= (@next_free_label_number += 1) emit(label.reference) else if @structured_key_prefix and not obj.is_a? String emit(@structured_key_prefix) @structured_key_prefix = nil end emit(new_label_for(obj)) yield end end def emit(s) @result << s @recent_nl = false unless s.kind_of?(Label) end def nl(s='') emit(@indent || "\n") unless @recent_nl emit(s) @recent_nl = true end def to_s @result.join end def prefix_structured_keys(x) @structured_key_prefix = x yield nl unless @structured_key_prefix @structured_key_prefix = nil end end ################################################################ # # Behavior for custom classes # ################################################################ class Object def to_yaml_properties - instance_variables.sort # Default YAML behavior + instance_variables # default YAML behaviour. end def yaml_property_munge(x) x end def zamlized_class_name(root) cls = self.class "!ruby/#{root.name.downcase}#{cls == root ? '' : ":#{cls.respond_to?(:name) ? cls.name : cls}"}" end def to_zaml(z) z.first_time_only(self) { z.emit(zamlized_class_name(Object)) z.nested { instance_variables = to_yaml_properties if instance_variables.empty? z.emit(" {}") else instance_variables.each { |v| z.nl v[1..-1].to_zaml(z) # Remove leading '@' z.emit(': ') yaml_property_munge(instance_variable_get(v)).to_zaml(z) } end } } end end ################################################################ # # Behavior for built-in classes # ################################################################ class NilClass def to_zaml(z) z.emit('') # NOTE: blank turns into nil in YAML.load end end class Symbol def to_zaml(z) z.emit(self.inspect) end end class TrueClass def to_zaml(z) z.emit('true') end end class FalseClass def to_zaml(z) z.emit('false') end end class Numeric def to_zaml(z) z.emit(self) end end class Regexp def to_zaml(z) z.first_time_only(self) { z.emit("#{zamlized_class_name(Regexp)} #{inspect}") } end end class Exception def to_zaml(z) z.emit(zamlized_class_name(Exception)) z.nested { z.nl("message: ") message.to_zaml(z) } end # # Monkey patch for buggy Exception restore in YAML # # This makes it work for now but is not very future-proof; if things # change we'll most likely want to remove this. To mitigate the risks # as much as possible, we test for the bug before appling the patch. # if respond_to? :yaml_new and yaml_new(self, :tag, "message" => "blurp").message != "blurp" def self.yaml_new( klass, tag, val ) o = YAML.object_maker( klass, {} ).exception(val.delete( 'message')) val.each_pair do |k,v| o.instance_variable_set("@#{k}", v) end o end end end class String ZAML_ESCAPES = %w{\x00 \x01 \x02 \x03 \x04 \x05 \x06 \a \x08 \t \n \v \f \r \x0e \x0f \x10 \x11 \x12 \x13 \x14 \x15 \x16 \x17 \x18 \x19 \x1a \e \x1c \x1d \x1e \x1f } def escaped_for_zaml # JJM (Note the trailing dots to construct a multi-line method chain.) This # code is meant to escape all bytes which are not ASCII-8BIT printable # characters. Multi-byte unicode characters are handled just fine because # each byte of the character results in an escaped string emitted to the # YAML stream. When the YAML is de-serialized back into a String the bytes # will be reconstructed properly into the unicode character. self.to_ascii8bit.gsub( /\x5C/n, "\\\\\\" ). # Demi-kludge for Maglev/rubinius; the regexp should be /\\/ but parsetree chokes on that. gsub( /"/n, "\\\"" ). gsub( /([\x00-\x1F])/n ) { |x| ZAML_ESCAPES[ x.unpack("C")[0] ] }. gsub( /([\x80-\xFF])/n ) { |x| "\\x#{x.unpack("C")[0].to_s(16)}" } end def to_zaml(z) z.first_time_only(self) { hex_num = '0x[a-f\d]+' float = '\d+\.?\d*' num = "[-+]?(?:#{float}|#{hex_num})" case when self == '' z.emit('""') # when self =~ /[\x00-\x08\x0B\x0C\x0E-\x1F\x80-\xFF]/ # z.emit("!binary |\n") # z.emit([self].pack("m*")) when ( (self =~ /\A(true|false|yes|no|on|null|off|#{num}(:#{num})*|!|=|~)$/i) or (self =~ /\A\n* /) or (self =~ /[\s:]$/) or (self =~ /^[>|][-+\d]*\s/i) or (self[-1..-1] =~ /\s/) or # This regular expression assumes the string is a byte sequence. # It does not concern itself with characters so we convert the string # to ASCII-8BIT for Ruby 1.9 to match up encodings. (self.to_ascii8bit=~ /[\x00-\x08\x0B\x0C\x0E-\x1F\x80-\xFF]/n) or (self =~ /[,\[\]\{\}\r\t]|:\s|\s#/) or (self =~ /\A([-:?!#&*'"]|<<|%.+:.)/) ) z.emit("\"#{escaped_for_zaml}\"") when self =~ /\n/ if self[-1..-1] == "\n" then z.emit('|+') else z.emit('|-') end z.nested { split("\n",-1).each { |line| z.nl; z.emit(line.chomp("\n")) } } else z.emit(self) end } end # Return a guranteed ASCII-8BIT encoding for Ruby 1.9 This is a helper # method for other methods that perform regular expressions against byte # sequences deliberately rather than dealing with characters. # The method may or may not return a new instance. def to_ascii8bit if self.respond_to?(:encoding) and self.encoding.name != "ASCII-8BIT" then str = self.dup str.force_encoding("ASCII-8BIT") return str else return self end end end class Hash def to_zaml(z) z.first_time_only(self) { z.nested { if empty? z.emit('{}') else each_pair { |k, v| z.nl z.prefix_structured_keys('? ') { k.to_zaml(z) } z.emit(': ') v.to_zaml(z) } end } } end end class Array def to_zaml(z) z.first_time_only(self) { z.nested { if empty? z.emit('[]') else each { |v| z.nl('- '); v.to_zaml(z) } end } } end end class Time def to_zaml(z) # 2008-12-06 10:06:51.373758 -07:00 ms = ("%0.6f" % (usec * 1e-6)).sub(/^\d+\./,'') offset = "%+0.2i:%0.2i" % [utc_offset / 3600, (utc_offset / 60) % 60] z.emit(self.strftime("%Y-%m-%d %H:%M:%S.#{ms} #{offset}")) end end class Date def to_zaml(z) z.emit(strftime('%Y-%m-%d')) end end class Range def to_zaml(z) z.first_time_only(self) { z.emit(zamlized_class_name(Range)) z.nested { z.nl z.emit('begin: ') z.emit(first) z.nl z.emit('end: ') z.emit(last) z.nl z.emit('excl: ') z.emit(exclude_end?) } } end end diff --git a/spec/unit/resource/status_spec.rb b/spec/unit/resource/status_spec.rb index 18e3359df..922f9edad 100755 --- a/spec/unit/resource/status_spec.rb +++ b/spec/unit/resource/status_spec.rb @@ -1,154 +1,154 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/resource/status' describe Puppet::Resource::Status do include PuppetSpec::Files before do @resource = Puppet::Type.type(:file).new :path => make_absolute("/my/file") @status = Puppet::Resource::Status.new(@resource) end it "should compute type and title correctly" do @status.resource_type.should == "File" @status.title.should == make_absolute("/my/file") end [:node, :file, :line, :current_values, :status, :evaluation_time].each do |attr| it "should support #{attr}" do @status.send(attr.to_s + "=", "foo") @status.send(attr).should == "foo" end end [:skipped, :failed, :restarted, :failed_to_restart, :changed, :out_of_sync, :scheduled].each do |attr| it "should support #{attr}" do @status.send(attr.to_s + "=", "foo") @status.send(attr).should == "foo" end it "should have a boolean method for determining whehter it was #{attr}" do @status.send(attr.to_s + "=", "foo") @status.should send("be_#{attr}") end end it "should accept a resource at initialization" do Puppet::Resource::Status.new(@resource).resource.should_not be_nil end it "should set its source description to the resource's path" do @resource.expects(:path).returns "/my/path" Puppet::Resource::Status.new(@resource).source_description.should == "/my/path" end [:file, :line].each do |attr| it "should copy the resource's #{attr}" do @resource.expects(attr).returns "foo" Puppet::Resource::Status.new(@resource).send(attr).should == "foo" end end it "should copy the resource's tags" do @resource.expects(:tags).returns %w{foo bar} Puppet::Resource::Status.new(@resource).tags.should == %w{foo bar} end it "should always convert the resource to a string" do @resource.expects(:to_s).returns "foo" Puppet::Resource::Status.new(@resource).resource.should == "foo" end it "should support tags" do Puppet::Resource::Status.ancestors.should include(Puppet::Util::Tagging) end it "should create a timestamp at its creation time" do @status.time.should be_instance_of(Time) end describe "when sending logs" do before do Puppet::Util::Log.stubs(:new) end it "should set the tags to the event tags" do Puppet::Util::Log.expects(:new).with { |args| args[:tags] == %w{one two} } @status.stubs(:tags).returns %w{one two} @status.send_log :notice, "my message" end [:file, :line].each do |attr| it "should pass the #{attr}" do Puppet::Util::Log.expects(:new).with { |args| args[attr] == "my val" } @status.send(attr.to_s + "=", "my val") @status.send_log :notice, "my message" end end it "should use the source description as the source" do Puppet::Util::Log.expects(:new).with { |args| args[:source] == "my source" } @status.stubs(:source_description).returns "my source" @status.send_log :notice, "my message" end end it "should support adding events" do event = Puppet::Transaction::Event.new(:name => :foobar) @status.add_event(event) @status.events.should == [event] end it "should use '<<' to add events" do event = Puppet::Transaction::Event.new(:name => :foobar) (@status << event).should equal(@status) @status.events.should == [event] end it "should count the number of successful events and set changed" do 3.times{ @status << Puppet::Transaction::Event.new(:status => 'success') } @status.change_count.should == 3 @status.changed.should == true @status.out_of_sync.should == true end it "should not start with any changes" do @status.change_count.should == 0 @status.changed.should == false @status.out_of_sync.should == false end it "should not treat failure, audit, or noop events as changed" do ['failure', 'audit', 'noop'].each do |s| @status << Puppet::Transaction::Event.new(:status => s) end @status.change_count.should == 0 @status.changed.should == false end it "should not treat audit events as out of sync" do @status << Puppet::Transaction::Event.new(:status => 'audit') @status.out_of_sync_count.should == 0 @status.out_of_sync.should == false end ['failure', 'noop', 'success'].each do |event_status| it "should treat #{event_status} events as out of sync" do 3.times do @status << Puppet::Transaction::Event.new(:status => event_status) end @status.out_of_sync_count.should == 3 @status.out_of_sync.should == true end end describe "When converting to YAML", :'fails_on_ruby_1.9.2' => true do it "should include only documented attributes" do @status.file = "/foo.rb" @status.line = 27 @status.evaluation_time = 2.7 @status.tags = %w{one two} - @status.to_yaml_properties.should == Puppet::Resource::Status::YAML_ATTRIBUTES.sort + @status.to_yaml_properties.should =~ Puppet::Resource::Status::YAML_ATTRIBUTES end end end diff --git a/spec/unit/transaction/event_spec.rb b/spec/unit/transaction/event_spec.rb index 5f7f367b4..38a5ecc1c 100755 --- a/spec/unit/transaction/event_spec.rb +++ b/spec/unit/transaction/event_spec.rb @@ -1,128 +1,128 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/transaction/event' describe Puppet::Transaction::Event do include PuppetSpec::Files [:previous_value, :desired_value, :property, :resource, :name, :message, :file, :line, :tags, :audited].each do |attr| it "should support #{attr}", :'fails_on_ruby_1.9.2' => true do event = Puppet::Transaction::Event.new event.send(attr.to_s + "=", "foo") event.send(attr).should == "foo" end end it "should always convert the property to a string" do Puppet::Transaction::Event.new(:property => :foo).property.should == "foo" end it "should always convert the resource to a string", :'fails_on_ruby_1.9.2' => true do Puppet::Transaction::Event.new(:resource => :foo).resource.should == "foo" end it "should produce the message when converted to a string" do event = Puppet::Transaction::Event.new event.expects(:message).returns "my message" event.to_s.should == "my message" end it "should support 'status'" do event = Puppet::Transaction::Event.new event.status = "success" event.status.should == "success" end it "should fail if the status is not to 'audit', 'noop', 'success', or 'failure" do event = Puppet::Transaction::Event.new lambda { event.status = "foo" }.should raise_error(ArgumentError) end it "should support tags" do Puppet::Transaction::Event.ancestors.should include(Puppet::Util::Tagging) end it "should create a timestamp at its creation time" do Puppet::Transaction::Event.new.time.should be_instance_of(Time) end describe "audit property" do it "should default to false" do Puppet::Transaction::Event.new.audited.should == false end end describe "when sending logs" do before do Puppet::Util::Log.stubs(:new) end it "should set the level to the resources's log level if the event status is 'success' and a resource is available" do resource = stub 'resource' resource.expects(:[]).with(:loglevel).returns :myloglevel Puppet::Util::Log.expects(:create).with { |args| args[:level] == :myloglevel } Puppet::Transaction::Event.new(:status => "success", :resource => resource).send_log end it "should set the level to 'notice' if the event status is 'success' and no resource is available" do Puppet::Util::Log.expects(:new).with { |args| args[:level] == :notice } Puppet::Transaction::Event.new(:status => "success").send_log end it "should set the level to 'notice' if the event status is 'noop'" do Puppet::Util::Log.expects(:new).with { |args| args[:level] == :notice } Puppet::Transaction::Event.new(:status => "noop").send_log end it "should set the level to 'err' if the event status is 'failure'" do Puppet::Util::Log.expects(:new).with { |args| args[:level] == :err } Puppet::Transaction::Event.new(:status => "failure").send_log end it "should set the 'message' to the event log" do Puppet::Util::Log.expects(:new).with { |args| args[:message] == "my message" } Puppet::Transaction::Event.new(:message => "my message").send_log end it "should set the tags to the event tags" do Puppet::Util::Log.expects(:new).with { |args| args[:tags] == %w{one two} } Puppet::Transaction::Event.new(:tags => %w{one two}).send_log end [:file, :line].each do |attr| it "should pass the #{attr}" do Puppet::Util::Log.expects(:new).with { |args| args[attr] == "my val" } Puppet::Transaction::Event.new(attr => "my val").send_log end end it "should use the source description as the source if one is set", :'fails_on_ruby_1.9.2' => true do Puppet::Util::Log.expects(:new).with { |args| args[:source] == "/my/param" } Puppet::Transaction::Event.new(:source_description => "/my/param", :resource => "Foo[bar]", :property => "foo").send_log end it "should use the property as the source if one is available and no source description is set", :'fails_on_ruby_1.9.2' => true do Puppet::Util::Log.expects(:new).with { |args| args[:source] == "foo" } Puppet::Transaction::Event.new(:resource => "Foo[bar]", :property => "foo").send_log end it "should use the property as the source if one is available and no property or source description is set", :'fails_on_ruby_1.9.2' => true do Puppet::Util::Log.expects(:new).with { |args| args[:source] == "Foo[bar]" } Puppet::Transaction::Event.new(:resource => "Foo[bar]").send_log end end describe "When converting to YAML" do it "should include only documented attributes" do resource = Puppet::Type.type(:file).new(:title => make_absolute("/tmp/foo")) event = Puppet::Transaction::Event.new(:source_description => "/my/param", :resource => resource, :file => "/foo.rb", :line => 27, :tags => %w{one two}, :desired_value => 7, :historical_value => 'Brazil', :message => "Help I'm trapped in a spec test", :name => :mode_changed, :previous_value => 6, :property => :mode, :status => 'success') - event.to_yaml_properties.should == Puppet::Transaction::Event::YAML_ATTRIBUTES.sort + event.to_yaml_properties.should =~ Puppet::Transaction::Event::YAML_ATTRIBUTES end end end diff --git a/spec/unit/util/log_spec.rb b/spec/unit/util/log_spec.rb index 52d3d60b3..ca7cbaf80 100755 --- a/spec/unit/util/log_spec.rb +++ b/spec/unit/util/log_spec.rb @@ -1,307 +1,307 @@ #!/usr/bin/env ruby -S rspec require 'spec_helper' require 'puppet/util/log' describe Puppet::Util::Log do include PuppetSpec::Files it "should write a given message to the specified destination" do arraydest = [] Puppet::Util::Log.newdestination(Puppet::Test::LogCollector.new(arraydest)) Puppet::Util::Log.new(:level => :notice, :message => "foo") message = arraydest.last.message message.should == "foo" end describe ".setup_default" do it "should default to :syslog" do Puppet.features.stubs(:syslog?).returns(true) Puppet::Util::Log.expects(:newdestination).with(:syslog) Puppet::Util::Log.setup_default end it "should fall back to :eventlog" do Puppet.features.stubs(:syslog?).returns(false) Puppet.features.stubs(:eventlog?).returns(true) Puppet::Util::Log.expects(:newdestination).with(:eventlog) Puppet::Util::Log.setup_default end it "should fall back to :file" do Puppet.features.stubs(:syslog?).returns(false) Puppet.features.stubs(:eventlog?).returns(false) Puppet::Util::Log.expects(:newdestination).with(Puppet[:puppetdlog]) Puppet::Util::Log.setup_default end end describe Puppet::Util::Log::DestConsole do before do @console = Puppet::Util::Log::DestConsole.new @console.stubs(:console_has_color?).returns(true) end it "should colorize if Puppet[:color] is :ansi" do Puppet[:color] = :ansi @console.colorize(:alert, "abc").should == "\e[0;31mabc\e[0m" end it "should colorize if Puppet[:color] is 'yes'" do Puppet[:color] = "yes" @console.colorize(:alert, "abc").should == "\e[0;31mabc\e[0m" end it "should htmlize if Puppet[:color] is :html" do Puppet[:color] = :html @console.colorize(:alert, "abc").should == "abc" end it "should do nothing if Puppet[:color] is false" do Puppet[:color] = false @console.colorize(:alert, "abc").should == "abc" end it "should do nothing if Puppet[:color] is invalid" do Puppet[:color] = "invalid option" @console.colorize(:alert, "abc").should == "abc" end end describe Puppet::Util::Log::DestSyslog do before do @syslog = Puppet::Util::Log::DestSyslog.new end end describe Puppet::Util::Log::DestEventlog, :if => Puppet.features.eventlog? do before :each do Win32::EventLog.stubs(:open).returns(mock 'mylog') Win32::EventLog.stubs(:report_event) Win32::EventLog.stubs(:close) Puppet.features.stubs(:eventlog?).returns(true) end it "should restrict its suitability" do Puppet.features.expects(:eventlog?).returns(false) Puppet::Util::Log::DestEventlog.suitable?('whatever').should == false end it "should open the 'Application' event log" do Win32::EventLog.expects(:open).with('Application') Puppet::Util::Log.newdestination(:eventlog) end it "should close the event log" do log = mock('myeventlog') log.expects(:close) Win32::EventLog.expects(:open).returns(log) Puppet::Util::Log.newdestination(:eventlog) Puppet::Util::Log.close(:eventlog) end it "should handle each puppet log level" do log = Puppet::Util::Log::DestEventlog.new Puppet::Util::Log.eachlevel do |level| log.to_native(level).should be_is_a(Array) end end end describe "instances" do before do Puppet::Util::Log.stubs(:newmessage) end [:level, :message, :time, :remote].each do |attr| it "should have a #{attr} attribute" do log = Puppet::Util::Log.new :level => :notice, :message => "A test message" log.should respond_to(attr) log.should respond_to(attr.to_s + "=") end end it "should fail if created without a level" do lambda { Puppet::Util::Log.new(:message => "A test message") }.should raise_error(ArgumentError) end it "should fail if created without a message" do lambda { Puppet::Util::Log.new(:level => :notice) }.should raise_error(ArgumentError) end it "should make available the level passed in at initialization" do Puppet::Util::Log.new(:level => :notice, :message => "A test message").level.should == :notice end it "should make available the message passed in at initialization" do Puppet::Util::Log.new(:level => :notice, :message => "A test message").message.should == "A test message" end # LAK:NOTE I don't know why this behavior is here, I'm just testing what's in the code, # at least at first. it "should always convert messages to strings" do Puppet::Util::Log.new(:level => :notice, :message => :foo).message.should == "foo" end it "should flush the log queue when the first destination is specified" do Puppet::Util::Log.close_all Puppet::Util::Log.expects(:flushqueue) Puppet::Util::Log.newdestination(:console) end it "should convert the level to a symbol if it's passed in as a string" do Puppet::Util::Log.new(:level => "notice", :message => :foo).level.should == :notice end it "should fail if the level is not a symbol or string", :'fails_on_ruby_1.9.2' => true do lambda { Puppet::Util::Log.new(:level => 50, :message => :foo) }.should raise_error(ArgumentError) end it "should fail if the provided level is not valid" do Puppet::Util::Log.expects(:validlevel?).with(:notice).returns false lambda { Puppet::Util::Log.new(:level => :notice, :message => :foo) }.should raise_error(ArgumentError) end it "should set its time to the initialization time" do time = mock 'time' Time.expects(:now).returns time Puppet::Util::Log.new(:level => "notice", :message => :foo).time.should equal(time) end it "should make available any passed-in tags" do log = Puppet::Util::Log.new(:level => "notice", :message => :foo, :tags => %w{foo bar}) log.tags.should be_include("foo") log.tags.should be_include("bar") end it "should use an passed-in source" do Puppet::Util::Log.any_instance.expects(:source=).with "foo" Puppet::Util::Log.new(:level => "notice", :message => :foo, :source => "foo") end [:file, :line].each do |attr| it "should use #{attr} if provided" do Puppet::Util::Log.any_instance.expects(attr.to_s + "=").with "foo" Puppet::Util::Log.new(:level => "notice", :message => :foo, attr => "foo") end end it "should default to 'Puppet' as its source" do Puppet::Util::Log.new(:level => "notice", :message => :foo).source.should == "Puppet" end it "should register itself with Log" do Puppet::Util::Log.expects(:newmessage) Puppet::Util::Log.new(:level => "notice", :message => :foo) end it "should update Log autoflush when Puppet[:autoflush] is set" do Puppet::Util::Log.expects(:autoflush=).once.with(true) Puppet[:autoflush] = true end it "should have a method for determining if a tag is present" do Puppet::Util::Log.new(:level => "notice", :message => :foo).should respond_to(:tagged?) end it "should match a tag if any of the tags are equivalent to the passed tag as a string" do Puppet::Util::Log.new(:level => "notice", :message => :foo, :tags => %w{one two}).should be_tagged(:one) end it "should tag itself with its log level" do Puppet::Util::Log.new(:level => "notice", :message => :foo).should be_tagged(:notice) end it "should return its message when converted to a string" do Puppet::Util::Log.new(:level => "notice", :message => :foo).to_s.should == "foo" end it "should include its time, source, level, and message when prepared for reporting" do log = Puppet::Util::Log.new(:level => "notice", :message => :foo) report = log.to_report report.should be_include("notice") report.should be_include("foo") report.should be_include(log.source) report.should be_include(log.time.to_s) end it "should not create unsuitable log destinations" do Puppet.features.stubs(:syslog?).returns(false) Puppet::Util::Log::DestSyslog.expects(:suitable?) Puppet::Util::Log::DestSyslog.expects(:new).never Puppet::Util::Log.newdestination(:syslog) end describe "when setting the source as a RAL object" do it "should tag itself with any tags the source has" do source = Puppet::Type.type(:file).new :path => make_absolute("/foo/bar") log = Puppet::Util::Log.new(:level => "notice", :message => :foo, :source => source) source.tags.each do |tag| log.tags.should be_include(tag) end end it "should use the source_descriptors" do source = stub "source" source.stubs(:source_descriptors).returns(:tags => ["tag","tag2"], :path => "path", :version => 100) log = Puppet::Util::Log.new(:level => "notice", :message => :foo) log.expects(:tag).with("tag") log.expects(:tag).with("tag2") log.source = source log.source.should == "path" end it "should copy over any file and line information" do source = Puppet::Type.type(:file).new :path => make_absolute("/foo/bar") source.file = "/my/file" source.line = 50 log = Puppet::Util::Log.new(:level => "notice", :message => :foo, :source => source) log.file.should == "/my/file" log.line.should == 50 end end describe "when setting the source as a non-RAL object" do it "should not try to copy over file, version, line, or tag information" do source = Puppet::Module.new("foo") source.expects(:file).never log = Puppet::Util::Log.new(:level => "notice", :message => :foo, :source => source) end end end describe "to_yaml", :'fails_on_ruby_1.9.2' => true do it "should not include the @version attribute" do log = Puppet::Util::Log.new(:level => "notice", :message => :foo, :version => 100) log.to_yaml_properties.should_not include('@version') end it "should include attributes @level, @message, @source, @tags, and @time" do log = Puppet::Util::Log.new(:level => "notice", :message => :foo, :version => 100) - log.to_yaml_properties.should == %w{@level @message @source @tags @time} + log.to_yaml_properties.should =~ %w{@level @message @source @tags @time} end it "should include attributes @file and @line if specified" do log = Puppet::Util::Log.new(:level => "notice", :message => :foo, :file => "foo", :line => 35) log.to_yaml_properties.should include('@file') log.to_yaml_properties.should include('@line') end end end