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