Significantly reworking the internals of the fileparsing code. It now

passes around an instance of a FileRecord, rather than just a hash, which I
think makes it much easier to understand.

Moved the sshkey parsed provider test to its own directory and made it better.

This work is all being done so I can move cron jobs to using providers
instead of the current unmaintainable state of affairs.



git-svn-id: https://reductivelabs.com/svn/puppet/trunk@2281 980ebf18-57e1-0310-9a29-db15c13687c0
This commit is contained in:
luke 2007-03-14 17:49:19 +00:00
Родитель b05ae2ae12
Коммит df4595e98f
14 изменённых файлов: 759 добавлений и 673 удалений

Просмотреть файл

@ -1,4 +1,7 @@
0.22.2 (grover)
Redid some of the internals of the ParsedFile provider base class.
It now passes a FileRecord around instead of a hash.
Fixing a bug related to link recursion that caused link directories
to always be considered out of sync.

Просмотреть файл

@ -123,6 +123,22 @@ class Puppet::Provider
end
end
# Create getter/setter methods for each property our model supports.
# They all get stored in @property_hash. This method is useful
# for those providers that use prefetch and flush.
def self.mkmodelmethods
[model.validproperties, model.parameters].flatten.each do |attr|
attr = symbolize(attr)
define_method(attr) do
@property_hash[attr] || :absent
end
define_method(attr.to_s + "=") do |val|
@property_hash[attr] = val
end
end
end
self.initvars
# Check whether this implementation is suitable for our platform.
@ -208,6 +224,7 @@ class Puppet::Provider
def initialize(model)
@model = model
@property_hash = {}
end
def name

Просмотреть файл

@ -0,0 +1,90 @@
require 'puppet/provider/parsedfile'
tab = case Facter.value(:operatingsystem)
when "Solaris": :suntab
else
:crontab
end
Puppet::Type.type(:cron).provide(:crontab,
:parent => Puppet::Provider::ParsedFile,
:filetype => tab
) do
commands :crontab => "crontab"
attr_accessor :target
text_line :comment, :match => %r{^#}, :post_parse => proc { |hash|
if hash[:line] =~ /Puppet Name: (.+)\s*$/
hash[:name] = $1
end
}
text_line :blank, :match => /^\s+/
text_line :environment, :match => %r{/^\w+=/}
record_line :crontab, :fields => %w{minute hour weekday month monthday command},
:optional => %w{minute hour weekday month monthday}, :absent => "*"
#record_line :freebsd_special, :fields => %w{special command},
# :match => %r{^@(\w+)\s+(.+)}
# Apparently Freebsd will "helpfully" add a new TZ line to every
# single cron line, but not in all cases (e.g., it doesn't do it
# on my machine. This is my attempt to fix it so the TZ lines don't
# multiply.
#if tab =~ /^TZ=.+$/
# return tab.sub(/\n/, "\n" + self.header)
#else
# return self.header() + tab
#end
# Return the header placed at the top of each generated file, warning
# users that modifying this file manually is probably a bad idea.
def self.header
%{# HEADER This file was autogenerated at #{Time.now} by puppet. While it
# HEADER can still be managed manually, it is definitely not recommended.
# HEADER Note particularly that the comments starting with 'Puppet Name' should
# HEADER not be deleted, as doing so could cause duplicate cron jobs.\n}
end
# Collapse name and env records.
def self.prefetch_hook(records)
name = nil
envs = []
records.each { |record|
case record[:record_type]
when :comment:
if record[:name]
name = record[:name]
record[:skip] = true
end
when :environment:
if name
envs << record[:line]
record[:skip] = true
end
else
if name
record[:name] = name
name = nil
end
unless envs.empty?
record[:environment] = envs
envs = []
end
end
}.reject { |record| record[:skip] }
end
def user=(user)
self.target = target
end
def user
self.target
end
end
# $Id$

Просмотреть файл

@ -117,8 +117,8 @@ class Puppet::Provider::ParsedFile < Puppet::Provider
list.collect { |r| r[:name] }
end
# Create attribute methods for each of the model's non-metaparam attributes.
def self.model=(model)
# Override the default method with a lot more functionality.
def self.mkmodelmethods
[model.validproperties, model.parameters].flatten.each do |attr|
attr = symbolize(attr)
define_method(attr) do
@ -152,7 +152,12 @@ class Puppet::Provider::ParsedFile < Puppet::Provider
@property_hash[attr] = val
end
end
@model = model
end
# Always make the model methods.
def self.model=(model)
super
mkmodelmethods()
end
# Mark a target as modified so we know to flush it. This only gets
@ -179,12 +184,18 @@ class Puppet::Provider::ParsedFile < Puppet::Provider
# Prefetch an individual target.
def self.prefetch_target(target)
@records += retrieve(target).each do |r|
target_records = retrieve(target).each do |r|
r[:on_disk] = true
r[:target] = target
r[:ensure] = :present
end
if respond_to?(:prefetch_hook)
prefetch_hook(target_records)
end
@records += target_records
# Set current property on any existing resource instances.
target_records(target).find_all { |i| i.is_a?(Hash) }.each do |record|
# Find any model instances whose names match our instances.
@ -222,7 +233,6 @@ class Puppet::Provider::ParsedFile < Puppet::Provider
ensure
@target = old
end
end
end

Просмотреть файл

@ -1,6 +1,5 @@
require 'puppet/provider/parsedfile'
known = nil
case Facter.value(:operatingsystem)
when "Darwin": known = "/etc/ssh_known_hosts"
@ -15,30 +14,23 @@ Puppet::Type.type(:sshkey).provide(:parsed,
) do
text_line :comment, :match => /^#/
text_line :blank, :match => /^\s+/
record_line :parsed, :fields => %w{name type key}
# Override the line parsing a bit, so we can split the aliases out.
def self.parse_line(line)
hash = super
if hash[:name] =~ /,/
names = hash[:name].split(",")
hash[:name] = names.shift
hash[:alias] = names
end
hash
end
def self.to_line(hash)
if hash[:alias]
hash = hash.dup
names = [hash[:name], hash[:alias]].flatten
hash[:name] = [hash[:name], hash[:alias]].flatten.join(",")
hash.delete(:alias)
end
super(hash)
end
record_line :parsed, :fields => %w{name type key},
:post_parse => proc { |hash|
if hash[:name] =~ /,/
names = hash[:name].split(",")
hash[:name] = names.shift
hash[:alias] = names
end
},
:pre_gen => proc { |hash|
if hash[:alias]
names = [hash[:name], hash[:alias]].flatten
hash[:name] = [hash[:name], hash[:alias]].flatten.join(",")
hash.delete(:alias)
end
}
end
# $Id$

Просмотреть файл

@ -11,75 +11,11 @@ module Puppet
# and is used to manage the job.
newtype(:cron) do
# A stupid hack because Cron is the only parsed type that I haven't
# converted to using providers. This whole stupid type needs to
# be rewritten.
class CronHackParam < Puppet::Property::ParsedParam
# Normally this would retrieve the current value, but our property is not
# actually capable of doing so.
def retrieve
# If we've synced, then just copy the values over and return.
# This allows this property to behave like any other property.
if defined? @synced and @synced
# by default, we only copy over the first value.
@is = @synced
@synced = false
return
end
unless defined? @is and ! @is.nil?
@is = :absent
end
end
# If the ensure property is out of sync, it will always be called
# first, so I don't need to worry about that.
def sync(nostore = false)
ebase = @parent.class.name.to_s
tail = nil
if self.class.name == :ensure
# We're either creating or destroying the object
if @is == :absent
#@is = self.should
tail = "created"
# If we're creating it, then sync all of the other properties
# but tell them not to store (we'll store just once,
# at the end).
unless nostore
@parent.eachproperty { |property|
next if property == self or property.name == :ensure
property.sync(true)
}
end
elsif self.should == :absent
@parent.remove(true)
tail = "deleted"
end
else
# We don't do the work here, it gets done in 'store'
tail = "changed"
end
@synced = self.should
# This should really only be done once per run, rather than
# every time. I guess we need some kind of 'flush' mechanism.
if nostore
self.retrieve
else
@parent.store
end
return (ebase + "_" + tail).intern
end
end
# A base class for all of the Cron parameters, since they all have
# similar argument checking going on. We're stealing the base class
# from parsedtype, and we should probably subclass Cron from there,
# but it was just too annoying to do.
class CronParam < CronHackParam
class CronParam < Puppet::Property
class << self
attr_accessor :boundaries, :default
end
@ -215,14 +151,6 @@ module Puppet
end
end
# Override 'newproperty' so that all properties default to having the
# correct parent type
def self.newproperty(name, options = {}, &block)
options[:parent] ||= Puppet::Property::CronParam
super(name, options, &block)
end
# Somewhat uniquely, this property does not actually change anything -- it
# just calls +@parent.sync+, which writes out the whole cron tab for
# the user in question. There is no real way to change individual cron
@ -253,7 +181,7 @@ module Puppet
end
end
newproperty(:special, :parent => CronHackParam) do
newproperty(:special) do
desc "Special schedules only supported on FreeBSD."
def specials
@ -268,19 +196,19 @@ module Puppet
end
end
newproperty(:minute) do
newproperty(:minute, :parent => CronParam) do
self.boundaries = [0, 59]
desc "The minute at which to run the cron job.
Optional; if specified, must be between 0 and 59, inclusive."
end
newproperty(:hour) do
newproperty(:hour, :parent => CronParam) do
self.boundaries = [0, 23]
desc "The hour at which to run the cron job. Optional;
if specified, must be between 0 and 23, inclusive."
end
newproperty(:weekday) do
newproperty(:weekday, :parent => CronParam) do
def alpha
%w{sunday monday tuesday wednesday thursday friday saturday}
end
@ -290,7 +218,7 @@ module Puppet
0 being Sunday, or must be the name of the day (e.g., Tuesday)."
end
newproperty(:month) do
newproperty(:month, :parent => CronParam) do
def alpha
%w{january february march april may june july
august september october november december}
@ -300,13 +228,13 @@ module Puppet
must be between 1 and 12 or the month name (e.g., December)."
end
newproperty(:monthday) do
newproperty(:monthday, :parent => CronParam) do
self.boundaries = [1, 31]
desc "The day of the month on which to run the
command. Optional; if specified, must be between 1 and 31."
end
newproperty(:environment, :parent => CronHackParam) do
newproperty(:environment) do
desc "Any environment settings associated with this cron job. They
will be stored between the header and the job in the crontab. There
can be no guarantees that other, earlier settings will not also
@ -364,7 +292,7 @@ module Puppet
end
end
newparam(:user) do
newproperty(:user) do
desc "The user to run the command as. This user must
be allowed to run cron jobs, which is not currently checked by
Puppet.
@ -372,15 +300,6 @@ module Puppet
The user defaults to whomever Puppet is running as."
defaultto { ENV["USER"] }
def value=(value)
super
# Make sure the user is not an array
if @value.is_a? Array
@value = @value[0]
end
end
end
@doc = "Installs and manages cron jobs. All fields except the command
@ -405,58 +324,8 @@ module Puppet
}
"
@instances = {}
@tabs = {}
class << self
attr_accessor :filetype
def cronobj(name)
if defined? @tabs
return @tabs[name]
else
return nil
end
end
end
attr_accessor :uid
# In addition to removing the instances in @objects, Cron has to remove
# per-user cron tab information.
def self.clear
@instances = {}
@tabs = {}
super
end
def self.defaulttype
case Facter["operatingsystem"].value
when "Solaris":
return Puppet::Util::FileType.filetype(:suntab)
else
return Puppet::Util::FileType.filetype(:crontab)
end
end
self.filetype = self.defaulttype()
# Override the default Puppet::Type method, because instances
# also need to be deleted from the @instances hash
def self.delete(child)
if @instances.include?(child[:user])
if @instances[child[:user]].include?(child)
@instances[child[:user]].delete(child)
end
end
super
end
# Return the fields found in the cron tab.
def self.fields
return [:minute, :hour, :monthday, :month, :weekday, :command]
end
# Convert our hash to an object
def self.hash2obj(hash)
obj = nil
@ -506,33 +375,6 @@ module Puppet
instance(obj)
end
# Return the header placed at the top of each generated file, warning
# users that modifying this file manually is probably a bad idea.
def self.header
%{# HEADER This file was autogenerated at #{Time.now} by puppet. While it
# HEADER can still be managed manually, it is definitely not recommended.
# HEADER Note particularly that the comments starting with 'Puppet Name' should
# HEADER not be deleted, as doing so could cause duplicate cron jobs.\n}
end
def self.instance(obj)
user = obj[:user]
unless @instances.include?(user)
@instances[user] = []
end
@instances[user] << obj
end
def self.list
# Look for cron jobs for each user
Puppet::Type.type(:user).list_by_name.each { |user|
self.retrieve(user, false)
}
self.collect { |c| c }
end
# See if we can match the hash against an existing cron job.
def self.match(hash)
self.find_all { |obj|
@ -580,276 +422,6 @@ module Puppet
return false
end
# Parse a user's cron job into individual cron objects.
#
# Autogenerates names for any jobs that don't already have one; these
# names will get written back to the file.
#
# This method also stores existing comments, and it stores all cron
# jobs in order, mostly so that comments are retained in the order
# they were written and in proximity to the same jobs.
def self.parse(user, text)
count = 0
hash = {}
envs = []
text.chomp.split("\n").each { |line|
case line
when /^# Puppet Name: (.+)$/
hash[:name] = $1
next
when /^#/:
# add other comments to the list as they are
@instances[user] << line
next
when /^\s*(\w+)\s*=\s*(.+)\s*$/:
# Match env settings.
if hash[:name]
envs << line
else
@instances[user] << line
end
next
when /^@(\w+)\s+(.+)/ # FreeBSD special cron crap
fields().each do |field|
next if field == :command
hash[field] = :absent
end
hash[:special] = $1
hash[:command] = $2
else
if match = /^(\S+) (\S+) (\S+) (\S+) (\S+) (.+)$/.match(line)
fields().zip(match.captures).each { |param, value|
if value == "*"
hash[param] = [:absent]
else
if param == :command
hash[param] = [value]
else
# We always want the 'is' value to be an
# array
hash[param] = value.split(",")
end
end
}
else
# Don't fail on unmatched lines, just warn on them
# and skip them.
Puppet.warning "Could not match '%s'" % line
next
end
end
unless envs.empty?
# We have to dup here so that we don't remove the settings
# in @is on the object.
hash[:environment] = envs.dup
end
hash[:user] = user
# Now convert our hash to an object.
hash2obj(hash)
hash = {}
envs.clear
count += 1
}
end
# Retrieve a given user's cron job, using the @filetype's +retrieve+
# method. Returns nil if there was no cron job; else, returns the
# number of cron instances found.
def self.retrieve(user, checkuser = true)
# First make sure the user exists, unless told not to
if checkuser
begin
Puppet::Util.uid(user)
rescue ArgumentError
raise Puppet::Error, "User %s not found" % user
end
end
@tabs[user] ||= @filetype.new(user)
text = @tabs[user].read
if $? != 0
# there is no cron file
return nil
else
# Preemptively mark everything absent, so that retrieving it
# can mark it present again.
self.find_all { |obj|
obj[:user] == user
}.each { |obj|
obj.is = [:ensure, :absent]
}
# Get rid of the old instances, so we don't get duplicates
if @instances.include?(user)
@instances[user].clear
else
@instances[user] = []
end
self.parse(user, text)
end
end
# Remove a user's cron tab.
def self.remove(user)
@tabs[user] ||= @filetype.new(user)
@tabs[user].remove
end
# Store the user's cron tab. Collects the text of the new tab and
# sends it to the +@filetype+ module's +write+ function. Also adds
# header, warning users not to modify the file directly.
def self.store(user)
unless @instances.include?(user) or @objects.find do |n,o|
o[:user] == user
end
Puppet.notice "No cron instances for %s" % user
return
end
@tabs[user] ||= @filetype.new(user)
self.each do |inst|
next unless inst[:user] == user
unless (@instances[user] and @instances[user].include? inst)
@instances[user] ||= []
@instances[user] << inst
end
end
@tabs[user].write(self.tab(user))
end
# Collect all Cron instances for a given user and convert them
# into literal text.
def self.tab(user)
Puppet.info "Writing cron tab for %s" % user
if @instances.include?(user)
tab = @instances[user].reject { |obj|
if obj.is_a?(self) and obj.should(:ensure) == :absent
true
else
false
end
}.collect { |obj|
if obj.is_a? self
obj.to_record
else
obj.to_s
end
}.join("\n") + "\n"
# Apparently Freebsd will "helpfully" add a new TZ line to every
# single cron line, but not in all cases (e.g., it doesn't do it
# on my machine. This is my attempt to fix it so the TZ lines don't
# multiply.
if tab =~ /^TZ=.+$/
return tab.sub(/\n/, "\n" + self.header)
else
return self.header() + tab
end
else
Puppet.notice "No cron instances for %s" % user
end
end
# Return the tab object itself. Pretty much just used for testing.
def self.tabobj(user)
@tabs[user]
end
# Return the last time a given user's cron tab was loaded. Could
# be used for reducing writes, but currently is not.
def self.loaded?(user)
if @tabs.include?(user)
return @loaded[user].loaded
else
return nil
end
end
def create
# nothing
self.store
end
def destroy
# nothing, since the 'Cron.tab' method just doesn't write out
# crons whose 'ensure' states are set to 'absent'.
self.store
end
def exists?
obj = @parameters[:ensure] and obj.is == :present
end
# Override the default Puppet::Type method because we need to call
# the +@filetype+ retrieve method.
def retrieve
unless @parameters.include?(:user)
self.fail "You must specify the cron user"
end
self.class.retrieve(self[:user])
self.eachproperty { |st|
st.retrieve
}
end
# Write the entire user's cron tab out.
def store
self.class.store(self[:user])
end
# Convert the current object a cron-style string. Adds the cron name
# as a comment above the cron job, in the form '# Puppet Name: <name>'.
def to_record
hash = {}
# Collect all of the values that we have
self.class.fields().each { |param|
hash[param] = self.value(param)
unless hash[param]
devfail "Got no value for %s" % param
end
}
str = ""
str = "# Puppet Name: %s\n" % self.name
if env = @parameters[:environment] and env.should != :absent
envs = env.should
unless envs.is_a? Array
envs = [envs]
end
envs.each do |line| str += (line + "\n") end
end
line = nil
if special = self.value(:special)
line = str + "@%s %s" %
[special, self.value(:command)]
else
line = str + self.class.fields.collect { |f|
if hash[f] and hash[f] != :absent
hash[f]
else
"*"
end
}.join(" ")
end
return line
end
def value(name)
name = symbolize(name)
ret = nil

Просмотреть файл

@ -24,10 +24,75 @@
# You could then call 'parser.to_line(hash)' on any of those hashes to generate
# the text line again.
require 'puppet/util/methodhelper'
module Puppet::Util::FileParsing
include Puppet::Util
attr_writer :line_separator, :trailing_separator
class FileRecord
include Puppet::Util
include Puppet::Util::MethodHelper
attr_accessor :absent, :joiner, :rts,
:separator, :rollup, :name, :match
attr_reader :fields, :optional, :type
INVALID_FIELDS = [:record_type, :target, :on_disk]
# Customize this so we can do a bit of validation.
def fields=(fields)
@fields = fields.collect do |field|
r = symbolize(field)
if INVALID_FIELDS.include?(r)
raise ArgumentError.new("Cannot have fields named %s" % r)
end
r
end
end
def initialize(type, options = {}, &block)
@type = symbolize(type)
unless [:record, :text].include?(@type)
raise ArgumentError, "Invalid record type %s" % @type
end
set_options(options)
if self.type == :record
# Now set defaults.
self.absent ||= ""
self.separator ||= /\s+/
self.joiner ||= " "
self.optional ||= []
unless defined? @rollup
@rollup = true
end
end
if block_given?
meta_def(:process, &block)
end
end
# Customize this so we can do a bit of validation.
def optional=(optional)
@optional = optional.collect do |field|
symbolize(field)
end
end
# Create a hook that modifies the hash resulting from parsing.
def post_parse=(block)
meta_def(:post_parse, &block)
end
# Create a hook that modifies the hash just prior to generation.
def pre_gen=(block)
meta_def(:pre_gen, &block)
end
end
# Clear all existing record definitions. Only used for testing.
def clear_records
@record_types.clear
@ -35,44 +100,45 @@ module Puppet::Util::FileParsing
end
def fields(type)
type = symbolize(type)
if @record_types.include?(type)
@record_types[type][:fields].dup
if record = record_type(type)
record.fields.dup
else
nil
end
end
# Try to match a specific text line.
def handle_text_line(line, hash)
if line =~ hash[:match]
return {:record_type => hash[:name], :line => line}
def handle_text_line(line, record)
if line =~ record.match
return {:record_type => record.name, :line => line}
else
return nil
end
end
# Try to match a record.
def handle_record_line(line, hash)
if method = hash[:method]
if ret = send(method, line.dup)
ret[:record_type] = hash[:name]
def handle_record_line(line, record)
if record.respond_to?(:process)
if ret = record.send(:process, line.dup)
unless ret.is_a?(Hash)
raise Puppet::DevError,
"Process record type %s returned non-hash" % record.name
end
ret[:record_type] = record.name
return ret
else
return nil
end
elsif regex = hash[:match]
elsif regex = record.match
raise "Cannot use matches to handle records yet"
# In this case, we try to match the whole line and then use the
# match captures to get our fields.
if match = regex.match(line)
fields = []
ignore = hash[:ignore] || []
p match.captures
ignore = record.ignore || []
match.captures.each_with_index do |value, i|
fields << value unless ignore.include? i
end
p fields
nil
else
Puppet.info "Did not match %s" % line
@ -80,21 +146,29 @@ module Puppet::Util::FileParsing
end
else
ret = {}
sep = hash[:separator]
sep = record.separator
# String "helpfully" replaces ' ' with /\s+/ in splitting, so we
# have to work around it.
if sep == " "
sep = / /
end
hash[:fields].zip(line.split(sep)) do |param, value|
if value and value != ""
line_fields = line.split(sep)
record.fields.each do |param|
value = line_fields.shift
if value and value != record.absent
ret[param] = value
else
ret[param] = :absent
end
end
ret[:record_type] = hash[:name]
unless line_fields.empty? or ! record.rollup
last_field = record.fields[-1]
val = ([ret[last_field]] + line_fields).join(record.joiner)
ret[last_field] = val
end
ret[:record_type] = record.name
return ret
end
end
@ -127,18 +201,24 @@ module Puppet::Util::FileParsing
end
@record_order.each do |name|
hash = @record_types[name]
unless hash
raise Puppet::DevError, "Did not get hash for %s: %s" %
record = record_type(name)
unless record
raise Puppet::DevError, "Did not get record type for %s: %s" %
[name, @record_types.inspect]
end
method = "handle_%s_line" % hash[:type]
# These are basically either text or record lines.
method = "handle_%s_line" % record.type
if respond_to?(method)
if result = send(method, line, hash)
if result = send(method, line, record)
if record.respond_to?(:post_parse)
record.send(:post_parse, result)
end
return result
end
else
raise Puppet::DevError, "Somehow got invalid line type %s" % hash[:type]
raise Puppet::DevError,
"Somehow got invalid line type %s" % record.type
end
end
@ -162,42 +242,10 @@ module Puppet::Util::FileParsing
raise ArgumentError, "Must include a list of fields"
end
invalidfields = [:record_type, :target, :on_disk]
options[:fields] = options[:fields].collect do |field|
r = symbolize(field)
if invalidfields.include?(r)
raise ArgumentError.new("Cannot have fields named %s" % r)
end
r
end
record = FileRecord.new(:record, options, &block)
record.name = symbolize(name)
options[:absent] ||= ""
if options[:optional]
options[:optional] = options[:optional].collect { |f| symbolize(f) }
else
options[:optional] = []
end
options[:separator] ||= /\s+/
# Unless they specified a string-based joiner, just use a single
# space as the join value.
unless options[:separator].is_a?(String) or options[:joiner]
options[:joiner] = " "
end
if block_given?
method = "handle_record_line_%s" % name
if respond_to?(method)
raise "Already have a method defined for this record"
end
meta_def(method, &block)
options[:method] = method
end
new_line_type(name, :record, options)
new_line_type(record)
end
# Are there any record types defined?
@ -206,12 +254,15 @@ module Puppet::Util::FileParsing
end
# Define a new type of text record.
def text_line(name, options)
def text_line(name, options, &block)
unless options.include?(:match)
raise ArgumentError, "You must provide a :match regex for text lines"
end
new_line_type(name, :text, options)
record = FileRecord.new(:text, options, &block)
record.name = symbolize(name)
new_line_type(record)
end
# Generate a file from a bunch of hash records.
@ -227,20 +278,25 @@ module Puppet::Util::FileParsing
# Convert our parsed record into a text record.
def to_line(details)
unless type = @record_types[details[:record_type]]
unless record = record_type(details[:record_type])
raise ArgumentError, "Invalid record type %s" % details[:record_type]
end
case type[:type]
if record.respond_to?(:pre_gen)
details = details.dup
record.send(:pre_gen, details)
end
case record.type
when :text: return details[:line]
else
joinchar = type[:joiner] || type[:separator]
joinchar = record.joiner
line = type[:fields].collect { |field|
line = record.fields.collect { |field|
# If the field is marked absent, use the appropriate replacement
if details[field] == :absent or details[field].nil?
if type[:optional].include?(field)
type[:absent]
if record.optional.include?(field)
record.absent
else
raise ArgumentError, "Field %s is required" % field
end
@ -249,7 +305,7 @@ module Puppet::Util::FileParsing
end
}.reject { |c| c.nil?}.join(joinchar)
if regex = type[:rts]
if regex = record.rts
# If they say true, then use whitespace; else, use their regex.
if regex == true
regex = /\s+$/
@ -272,7 +328,7 @@ module Puppet::Util::FileParsing
def valid_attr?(type, attr)
type = symbolize(type)
if @record_types[type] and @record_types[type][:fields].include?(symbolize(attr))
if record = record_type(type) and record.fields.include?(symbolize(attr))
return true
else
if symbolize(attr) == :ensure
@ -285,22 +341,23 @@ module Puppet::Util::FileParsing
private
# Define a new type of record.
def new_line_type(name, type, options)
def new_line_type(record)
@record_types ||= {}
@record_order ||= []
name = symbolize(name)
if @record_types.include?(name)
raise ArgumentError, "Line type %s is already defined" % name
if @record_types.include?(record.name)
raise ArgumentError, "Line type %s is already defined" % record.name
end
options[:name] = name
options[:type] = type
@record_types[name] = options
@record_order << name
@record_types[record.name] = record
@record_order << record.name
return options
return record
end
# Retrive the record object.
def record_type(type)
@record_types[symbolize(type)]
end
end

Просмотреть файл

@ -15,7 +15,7 @@ module Puppet::Util::MethodHelper
begin
self.send(method, value)
rescue NoMethodError
self.fail "Invalid parameter %s to object class %s" %
raise ArgumentError, "Invalid parameter %s to object class %s" %
[param,self.class.to_s]
end
end

Просмотреть файл

@ -0,0 +1,173 @@
#!/usr/bin/env ruby
$:.unshift("../../../lib") if __FILE__ =~ /\.rb$/
require 'puppettest'
require 'mocha'
require 'puppettest/fileparsing'
require 'puppet/type/cron'
require 'puppet/provider/cron/crontab'
class TestCronParsedProvider < Test::Unit::TestCase
include PuppetTest
include PuppetTest::FileParsing
def setup
super
@provider = Puppet::Type.type(:cron).provider(:crontab)
@oldfiletype = @provider.filetype
end
def teardown
Puppet::Util::FileType.filetype(:ram).clear
@provider.filetype = @oldfiletype
@provider.clear
super
end
def test_parse_record
fields = [:month, :weekday, :monthday, :hour, :command, :minute]
{
"* * * * * /bin/echo" => {:command => "/bin/echo"},
"10 * * * * /bin/echo test" => {:minute => "10",
:command => "/bin/echo test"}
}.each do |line, should|
result = nil
assert_nothing_raised("Could not parse %s" % line.inspect) do
result = @provider.parse_line(line)
end
should[:record_type] = :crontab
fields.each do |field|
if should[field]
assert_equal(should[field], result[field],
"Did not parse %s in %s correctly" % [field, line.inspect])
else
assert_equal(:absent, result[field],
"Did not set %s absent in %s" % [field, line.inspect])
end
end
end
end
def test_prefetch_hook
count = 0
env = Proc.new do
count += 1
{:record_type => :environment, :line => "env%s=val" % count}
end
comment = Proc.new do |name|
count += 1
hash = {:record_type => :comment, :line => "comment %s" % count}
if name
hash[:name] = name
end
hash
end
record = Proc.new do
count += 1
{:record_type => :crontab, :command => "command%s" % count}
end
result = nil
args = []
# First try it with all three
args << comm = comment.call(false)
args << name = comment.call(true)
args << var = env.call()
args << line = record.call
args << sec = comment.call(false)
assert_nothing_raised do
result = @provider.prefetch_hook(args)
end
assert_equal([comm, line, sec], result,
"Did not remove name and var records")
assert_equal(name[:name], line[:name],
"did not set name in hook")
assert_equal([var[:line]], line[:environment],
"did not set env")
# Now try it with an env, a name, and a record
args.clear
args << var = env.call()
args << name = comment.call(true)
args << line = record.call
assert_nothing_raised do
result = @provider.prefetch_hook(args)
end
assert_equal([var, line], result,
"Removed var record")
assert_equal(name[:name], line[:name],
"did not set name in hook")
assert_nil(line[:environment], "incorrectly set env")
# try it with a comment, an env, and a record
args.clear
args << comm = comment.call(false)
args << var = env.call()
args << line = record.call
assert_nothing_raised do
result = @provider.prefetch_hook(args)
end
assert_equal([comm, var, line], result,
"Removed var record")
assert_nil(line[:name], "name got set somehow")
assert_nil(line[:environment], "env got set somehow")
# Try it with multiple records
args = []
should = []
args << startcom = comment.call(false)
should << startcom
args << startenv = env.call
should << startenv
args << name1 = comment.call(true)
args << env1s = env.call
args << env1m = env.call
args << line1 = record.call
should << line1
args << midcom = comment.call(false)
args << midenv = env.call
should << midcom << midenv
args << name2 = comment.call(true)
args << env2s = env.call
args << env2m = env.call
args << line2 = record.call
should << line2
args << endcom = comment.call(false)
args << endenv = env.call
should << endcom << endenv
assert_nothing_raised do
result = @provider.prefetch_hook(args)
end
assert_equal(should, result,
"Did not handle records correctly")
assert_equal(line1[:name], line1[:name], "incorrectly set first name")
assert_equal(line1[:environment], line1[:environment],
"incorrectly set first env")
assert_equal(line2[:name], line2[:name], "incorrectly set second name")
assert_equal(line2[:environment], line2[:environment],
"incorrectly set second env")
end
# A simple test to see if we can load the cron from disk.
def test_load
setme()
records = nil
assert_nothing_raised {
records = @provider.retrieve(@me)
}
assert_instance_of(Array, records, "did not get correct response")
end
end
# $Id$

Просмотреть файл

@ -3,6 +3,7 @@
$:.unshift("../../lib") if __FILE__ =~ /\.rb$/
require 'puppettest'
require 'mocha'
require 'puppettest/fileparsing'
require 'puppet/util/filetype'
require 'puppet/provider/parsedfile'
@ -513,11 +514,20 @@ class TestParsedFile < Test::Unit::TestCase
end
cleanup { @type.unprovide(:record) }
records = prov.parse("a d")
line = prov.parse_line("a d")
line = records.find { |r| r[:name] == "a" }
assert(line, "Could not find line")
assert_equal("a", line[:name], "field name was not set")
assert_equal(:absent, line[:one], "field one was not set to absent")
# Now use a different provider with a non-blank "absent"
prov = @type.provide(:cronstyle, :parent => Puppet::Provider::ParsedFile) do
record_line :cronstyle, :fields => %w{name one two},
:separator => "\s", :absent => "*"
end
cleanup { @type.unprovide(:cronstyle) }
line = prov.parse_line("a * d")
assert_equal("a", line[:name], "field name was not set")
assert_equal(:absent, line[:one], "field one was not set to absent")
end
@ -595,6 +605,31 @@ class TestParsedFile < Test::Unit::TestCase
assert(bill.insync?,
"An invalid field marked the record out of sync")
end
# Make sure we call the prefetch hook at the right place.
def test_prefetch_hook
prov = @type.provide(:test, :parent => Puppet::Provider::ParsedFile,
:filetype => :ram, :default_target => :yayness) do
def self.prefetch_hook(records)
end
record_line :test, :fields => %w{name two}
end
cleanup do @type.unprovide(:test) end
target = "target"
records = [{:target => "nope"}]
targeted = {:target => "target"}
prov.send(:instance_variable_set, "@records", records)
prov.expects(:retrieve).with(target).returns([targeted])
prov.expects(:target_records).with(target).returns([targeted])
prov.expects(:prefetch_hook).with([targeted])
prov.prefetch_target(target)
end
end
# $Id$

Просмотреть файл

@ -106,6 +106,7 @@ class TestProvider < Test::Unit::TestCase
provider.command(:fake)
end
Puppet[:trace] = false
assert_raise(Puppet::DevError) do
provider.command(:nosuchcmd)
end
@ -182,6 +183,33 @@ class TestProvider < Test::Unit::TestCase
assert(out =~ /Execution of/,
"Did not receive info wrapper on failure")
end
def test_mkmodelmethods
prov = newprovider
modeltype = Struct.new(:validproperties, :parameters)
m = modeltype.new([:prop1, :prop2], [:param1, :param2])
prov.model = m
assert_nothing_raised("could not call mkmodelmethods") do
prov.mkmodelmethods
end
obj = prov.new(nil)
%w{prop1 prop2 param1 param2}.each do |param|
assert(prov.public_method_defined?(param), "no getter for %s" % param)
assert(prov.public_method_defined?(param + "="), "no setter for %s" % param)
assert_equal(:absent, obj.send(param),
"%s did not default to :absent")
val = "testing %s" % param
assert_nothing_raised("Could not call setter for %s" % param) do
obj.send(param + "=", val)
end
assert_equal(val, obj.send(param),
"did not get correct value for %s" % param)
end
end
end
# $Id$

Просмотреть файл

@ -1,6 +1,6 @@
#!/usr/bin/env ruby
$:.unshift("../../lib") if __FILE__ =~ /\.rb$/
$:.unshift("../../../lib") if __FILE__ =~ /\.rb$/
require 'puppettest'
require 'puppettest/fileparsing'
@ -88,6 +88,24 @@ class TestParsedSSHKey < Test::Unit::TestCase
assert(key.name !~ /,/, "Aliases were not split out during parsing")
end
def test_hooks
result = nil
assert_nothing_raised("Could not call post hook") do
result = @provider.parse_line("one,two type key")
end
assert_equal("one", result[:name], "Did not call post hook")
assert_equal(%w{two}, result[:alias], "Did not call post hook")
assert_equal("one,two type key",
@provider.to_line(:record_type => :parsed,
:name => "one",
:alias => %w{two},
:type => "type",
:key => "key"),
"Did not use pre-hook when generating line"
)
end
end
# $Id$

Просмотреть файл

@ -15,15 +15,6 @@ class TestCron < Test::Unit::TestCase
# god i'm lazy
@crontype = Puppet.type(:cron)
@oldfiletype = @crontype.filetype
@fakefiletype = Puppet::Util::FileType.filetype(:ram)
@crontype.filetype = @fakefiletype
end
def teardown
@crontype.filetype = @oldfiletype
Puppet::Util::FileType.filetype(:ram).clear
super
end
# Back up the user's existing cron tab if they have one.
@ -107,13 +98,6 @@ class TestCron < Test::Unit::TestCase
assert_events([], comp)
end
# A simple test to see if we can load the cron from disk.
def test_load
assert_nothing_raised {
@crontype.retrieve(@me)
}
end
# Test that a cron job turns out as expected, by creating one and generating
# it directly
def test_simple_to_cron

Просмотреть файл

@ -15,23 +15,26 @@ class TestUtilFileParsing < Test::Unit::TestCase
include Puppet::Util::FileParsing
end
def test_lines
parser = FParser.new
def setup
super
@parser = FParser.new
end
assert_equal("\n", parser.line_separator,
def test_lines
assert_equal("\n", @parser.line_separator,
"Default separator was incorrect")
{"\n" => ["one two\nthree four", "one two\nthree four\n"],
"\t" => ["one two\tthree four", "one two\tthree four\t"],
}.each do |sep, tests|
assert_nothing_raised do
parser.line_separator = sep
@parser.line_separator = sep
end
assert_equal(sep, parser.line_separator,
assert_equal(sep, @parser.line_separator,
"Did not set separator")
tests.each do |test|
assert_equal(["one two", "three four"], parser.lines(test),
assert_equal(["one two", "three four"], @parser.lines(test),
"Incorrectly parsed %s" % test.inspect)
end
end
@ -39,9 +42,7 @@ class TestUtilFileParsing < Test::Unit::TestCase
# Make sure parse calls the appropriate methods or errors out
def test_parse
parser = FParser.new
parser.meta_def(:parse_line) do |line|
@parser.meta_def(:parse_line) do |line|
line.split(/\s+/)
end
@ -49,7 +50,7 @@ class TestUtilFileParsing < Test::Unit::TestCase
should = [%w{one line}, %w{two line}]
ret = nil
assert_nothing_raised do
ret = parser.parse(text)
ret = @parser.parse(text)
end
assert_equal(should, ret)
@ -57,80 +58,78 @@ class TestUtilFileParsing < Test::Unit::TestCase
# Make sure we correctly handle different kinds of text lines.
def test_text_line
parser = FParser.new
comment = "# this is a comment"
# Make sure it fails if no regex is passed
assert_raise(ArgumentError) do
parser.text_line :comment
@parser.text_line :comment
end
# define a text matching comment record
assert_nothing_raised do
parser.text_line :comment, :match => /^#/
@parser.text_line :comment, :match => /^#/
end
# Make sure it matches
assert_nothing_raised do
assert_equal({:record_type => :comment, :line => comment},
parser.parse_line(comment))
@parser.parse_line(comment))
end
# But not something else
assert_nothing_raised do
assert_nil(parser.parse_line("some other text"))
assert_nil(@parser.parse_line("some other text"))
end
# Now define another type and make sure we get the right one back
assert_nothing_raised do
parser.text_line :blank, :match => /^\s*$/
@parser.text_line :blank, :match => /^\s*$/
end
# The comment should still match
assert_nothing_raised do
assert_equal({:record_type => :comment, :line => comment},
parser.parse_line(comment))
@parser.parse_line(comment))
end
# As should our new line type
assert_nothing_raised do
assert_equal({:record_type => :blank, :line => ""},
parser.parse_line(""))
@parser.parse_line(""))
end
end
def test_parse_line
parser = FParser.new
Puppet[:trace] = false
comment = "# this is a comment"
# Make sure it fails if we don't have any record types defined
assert_raise(Puppet::DevError) do
parser.parse_line(comment)
@parser.parse_line(comment)
end
# Now define a text matching comment record
assert_nothing_raised do
parser.text_line :comment, :match => /^#/
@parser.text_line :comment, :match => /^#/
end
# And make sure we can't define another one with the same name
assert_raise(ArgumentError) do
parser.text_line :comment, :match => /^"/
@parser.text_line :comment, :match => /^"/
end
result = nil
assert_nothing_raised("Did not parse text line") do
result = parser.parse_line comment
result = @parser.parse_line comment
end
assert_equal({:record_type => :comment, :line => comment}, result)
# Make sure we just return nil on unmatched lines.
assert_nothing_raised("Did not parse text line") do
result = parser.parse_line "No match for this"
result = @parser.parse_line "No match for this"
end
assert_nil(result, "Somehow matched an empty line")
@ -138,63 +137,61 @@ class TestUtilFileParsing < Test::Unit::TestCase
# Now define another type of comment, and make sure both types get
# correctly returned as comments
assert_nothing_raised do
parser.text_line :comment2, :match => /^"/
@parser.text_line :comment2, :match => /^"/
end
assert_nothing_raised("Did not parse old comment") do
assert_equal({:record_type => :comment, :line => comment},
parser.parse_line(comment))
@parser.parse_line(comment))
end
comment = '" another type of comment'
assert_nothing_raised("Did not parse new comment") do
assert_equal({:record_type => :comment2, :line => comment},
parser.parse_line(comment))
@parser.parse_line(comment))
end
# Now define two overlapping record types and make sure we keep the
# correct order. We do first match, not longest match.
assert_nothing_raised do
parser.text_line :one, :match => /^y/
parser.text_line :two, :match => /^yay/
@parser.text_line :one, :match => /^y/
@parser.text_line :two, :match => /^yay/
end
assert_nothing_raised do
assert_equal({:record_type => :one, :line => "yayness"},
parser.parse_line("yayness"))
@parser.parse_line("yayness"))
end
end
def test_record_line
parser = FParser.new
tabrecord = "tab separated content"
spacerecord = "space separated content"
# Make sure we always require an appropriate set of options
[{:separator => "\t"}, {}, {:fields => %w{record_type}}].each do |opts|
assert_raise(ArgumentError, "Accepted %s" % opts.inspect) do
parser.record_line :record, opts
@parser.record_line :record, opts
end
end
# Verify that our default separator is tabs
tabs = nil
assert_nothing_raised do
tabs = parser.record_line :tabs, :fields => [:name, :first, :second]
tabs = @parser.record_line :tabs, :fields => [:name, :first, :second]
end
# Make sure out tab line gets matched
tabshould = {:record_type => :tabs, :name => "tab", :first => "separated",
:second => "content"}
assert_nothing_raised do
assert_equal(tabshould, parser.handle_record_line(tabrecord, tabs))
assert_equal(tabshould, @parser.handle_record_line(tabrecord, tabs))
end
# Now add our space-separated record type
spaces = nil
assert_nothing_raised do
spaces = parser.record_line :spaces, :fields => [:name, :first, :second]
spaces = @parser.record_line :spaces, :fields => [:name, :first, :second]
end
# Now make sure both lines parse correctly
@ -202,17 +199,15 @@ class TestUtilFileParsing < Test::Unit::TestCase
:first => "separated", :second => "content"}
assert_nothing_raised do
assert_equal(tabshould, parser.handle_record_line(tabrecord, tabs))
assert_equal(spaceshould, parser.handle_record_line(spacerecord, spaces))
assert_equal(tabshould, @parser.handle_record_line(tabrecord, tabs))
assert_equal(spaceshould, @parser.handle_record_line(spacerecord, spaces))
end
end
def test_to_line
parser = FParser.new
parser.text_line :comment, :match => /^#/
parser.text_line :blank, :match => /^\s*$/
parser.record_line :record, :fields => %w{name one two}, :joiner => "\t"
@parser.text_line :comment, :match => /^#/
@parser.text_line :blank, :match => /^\s*$/
@parser.record_line :record, :fields => %w{name one two}, :joiner => "\t"
johnny = {:record_type => :record, :name => "johnny", :one => "home",
:two => "yay"}
@ -236,7 +231,7 @@ class TestUtilFileParsing < Test::Unit::TestCase
records.each do |name, details|
result = nil
assert_nothing_raised do
result = parser.to_line(details)
result = @parser.to_line(details)
end
assert_equal(lines[name], result)
@ -248,55 +243,53 @@ class TestUtilFileParsing < Test::Unit::TestCase
ordered_records = order.collect { |name| records[name] }
# Make sure we default to a trailing separator
assert_equal(true, parser.trailing_separator,
assert_equal(true, @parser.trailing_separator,
"Did not default to a trailing separtor")
# Start without a trailing separator
parser.trailing_separator = false
@parser.trailing_separator = false
assert_nothing_raised do
assert_equal(file, parser.to_file(ordered_records))
assert_equal(file, @parser.to_file(ordered_records))
end
# Now with a trailing separator
file += "\n"
parser.trailing_separator = true
@parser.trailing_separator = true
assert_nothing_raised do
assert_equal(file, parser.to_file(ordered_records))
assert_equal(file, @parser.to_file(ordered_records))
end
# Now try it with a different separator, so we're not just catching
# defaults
file.gsub!("\n", "\t")
parser.line_separator = "\t"
@parser.line_separator = "\t"
assert_nothing_raised do
assert_equal(file, parser.to_file(ordered_records))
assert_equal(file, @parser.to_file(ordered_records))
end
end
# Make sure fields that are marked absent get replaced with the appropriate
# string.
def test_absent_fields
parser = FParser.new
options = nil
record = nil
assert_nothing_raised do
options = parser.record_line :record, :fields => %w{one two three},
record = @parser.record_line :record, :fields => %w{one two three},
:optional => %w{two three}
end
assert_equal("", options[:absent], "Did not set a default absent string")
assert_equal("", record.absent, "Did not set a default absent string")
result = nil
assert_nothing_raised do
result = parser.to_line(:record_type => :record,
result = @parser.to_line(:record_type => :record,
:one => "a", :two => :absent, :three => "b")
end
assert_equal("a b", result, "Absent was not correctly replaced")
# Now try using a different replacement character
options[:absent] = "*" # Because cron is a pain in my ass
record.absent = "*" # Because cron is a pain in my ass
assert_nothing_raised do
result = parser.to_line(:record_type => :record,
result = @parser.to_line(:record_type => :record,
:one => "a", :two => :absent, :three => "b")
end
@ -304,7 +297,7 @@ class TestUtilFileParsing < Test::Unit::TestCase
# Make sure we deal correctly with the string 'absent'
assert_nothing_raised do
result = parser.to_line(:record_type => :record,
result = @parser.to_line(:record_type => :record,
:one => "a", :two => "b", :three => 'absent')
end
@ -312,7 +305,7 @@ class TestUtilFileParsing < Test::Unit::TestCase
# And, of course, make sure we can swap things around.
assert_nothing_raised do
result = parser.to_line(:record_type => :record,
result = @parser.to_line(:record_type => :record,
:one => "a", :two => "b", :three => :absent)
end
@ -321,38 +314,36 @@ class TestUtilFileParsing < Test::Unit::TestCase
# Make sure we can specify a different join character than split character
def test_split_join_record_line
parser = FParser.new
check = proc do |start, record, final|
# Check parsing first
result = parser.parse_line(start)
result = @parser.parse_line(start)
[:one, :two].each do |param|
assert_equal(record[param], result[param],
"Did not correctly parse %s" % start.inspect)
end
# And generating
assert_equal(final, parser.to_line(result),
assert_equal(final, @parser.to_line(result),
"Did not correctly generate %s from %s" %
[final.inspect, record.inspect])
end
# First try it with symmetric characters
parser.record_line :symmetric, :fields => %w{one two},
@parser.record_line :symmetric, :fields => %w{one two},
:separator => " "
check.call "a b", {:one => "a", :two => "b"}, "a b"
parser.clear_records
@parser.clear_records
# Now assymetric but both strings
parser.record_line :asymmetric, :fields => %w{one two},
@parser.record_line :asymmetric, :fields => %w{one two},
:separator => "\t", :joiner => " "
check.call "a\tb", {:one => "a", :two => "b"}, "a b"
parser.clear_records
@parser.clear_records
# And assymmetric with a regex
parser.record_line :asymmetric2, :fields => %w{one two},
@parser.record_line :asymmetric2, :fields => %w{one two},
:separator => /\s+/, :joiner => " "
check.call "a\tb", {:one => "a", :two => "b"}, "a b"
@ -361,11 +352,9 @@ class TestUtilFileParsing < Test::Unit::TestCase
# Make sure we correctly regenerate files.
def test_to_file
parser = FParser.new
parser.text_line :comment, :match => /^#/
parser.text_line :blank, :match => /^\s*$/
parser.record_line :record, :fields => %w{name one two}
@parser.text_line :comment, :match => /^#/
@parser.text_line :blank, :match => /^\s*$/
@parser.record_line :record, :fields => %w{name one two}
text = "# This is a comment
@ -374,33 +363,29 @@ billy three four\n"
# Just parse and generate, to make sure it's isomorphic.
assert_nothing_raised do
assert_equal(text, parser.to_file(parser.parse(text)),
assert_equal(text, @parser.to_file(@parser.parse(text)),
"parsing was not isomorphic")
end
end
def test_valid_attrs
parser = FParser.new
@parser.record_line :record, :fields => %w{one two three}
parser.record_line :record, :fields => %w{one two three}
assert(parser.valid_attr?(:record, :one),
assert(@parser.valid_attr?(:record, :one),
"one was considered invalid")
assert(parser.valid_attr?(:record, :ensure),
assert(@parser.valid_attr?(:record, :ensure),
"ensure was considered invalid")
assert(! parser.valid_attr?(:record, :four),
assert(! @parser.valid_attr?(:record, :four),
"four was considered valid")
end
def test_record_blocks
parser = FParser.new
options = nil
assert_nothing_raised do
# Just do a simple test
options = parser.record_line :record,
options = @parser.record_line :record,
:fields => %w{name alias info} do |line|
line = line.dup
ret = {}
@ -423,9 +408,6 @@ billy three four\n"
end
end
assert(parser.respond_to?(:handle_record_line_record),
"Parser did not define record method")
values = {
:name => "tcpmux",
:description => "TCP port service multiplexer",
@ -446,7 +428,7 @@ billy three four\n"
}.each do |line, should|
result = nil
assert_nothing_raised do
result = parser.handle_record_line(line, options)
result = @parser.handle_record_line(line, options)
end
assert(result, "Did not get a result back for '%s'" % line)
should.each do |field|
@ -466,10 +448,8 @@ billy three four\n"
# Make sure we correctly handle optional fields. We'll skip this
# functionality until we really know we need it.
def test_optional_fields
parser = FParser.new
assert_nothing_raised do
parser.record_line :record,
@parser.record_line :record,
:fields => %w{one two three four},
:optional => %w{three four},
:absent => "*",
@ -479,13 +459,13 @@ billy three four\n"
["a b c d", "a b * d", "a b * *", "a b c *"].each do |line|
record = nil
assert_nothing_raised do
record = parser.parse_line(line)
record = @parser.parse_line(line)
end
# Now regenerate the line
newline = nil
assert_nothing_raised do
newline = parser.to_line(record)
newline = @parser.to_line(record)
end
# And make sure they're equal
@ -494,41 +474,39 @@ billy three four\n"
# Now make sure it pukes if we don't provide the required fields
assert_raise(ArgumentError) do
parser.to_line(:record_type => :record, :one => "yay")
@parser.to_line(:record_type => :record, :one => "yay")
end
end
def test_record_rts
parser = FParser.new
# Start with the default
assert_nothing_raised do
parser.record_line :record,
@parser.record_line :record,
:fields => %w{one two three four},
:optional => %w{three four}
end
assert_equal("a b ",
parser.to_line(:record_type => :record, :one => "a", :two => "b")
@parser.to_line(:record_type => :record, :one => "a", :two => "b")
)
# Now say yes to removing
parser.clear_records
@parser.clear_records
assert_nothing_raised do
parser.record_line :record,
@parser.record_line :record,
:fields => %w{one two three four},
:optional => %w{three four},
:rts => true
end
assert_equal("a b",
parser.to_line(:record_type => :record, :one => "a", :two => "b")
@parser.to_line(:record_type => :record, :one => "a", :two => "b")
)
# Lastly, try a regex
parser.clear_records
@parser.clear_records
assert_nothing_raised do
parser.record_line :record,
@parser.record_line :record,
:fields => %w{one two three four},
:optional => %w{three four},
:absent => "*",
@ -536,9 +514,138 @@ billy three four\n"
end
assert_equal("a b",
parser.to_line(:record_type => :record, :one => "a", :two => "b")
@parser.to_line(:record_type => :record, :one => "a", :two => "b")
)
end
# Make sure the last field can contain the separator, as crontabs do, and
# that we roll them all up by default.
def test_field_rollups
@parser.record_line :yes, :fields => %w{name one two}
result = nil
assert_nothing_raised do
result = @parser.send(:parse_line, "Name One Two Three")
end
assert_equal("Two Three", result[:two],
"Did not roll up last fields by default")
@parser = FParser.new
assert_nothing_raised("Could not create record that rolls up fields") do
@parser.record_line :no, :fields => %w{name one two}, :rollup => false
end
result = nil
assert_nothing_raised do
result = @parser.send(:parse_line, "Name One Two Three")
end
assert_equal("Two", result[:two],
"Rolled up last fields when rollup => false")
end
def test_text_blocks
record = nil
assert_nothing_raised do
record = @parser.text_line :name, :match => %r{^#} do |line|
{:line => line.upcase}
end
end
assert(record.respond_to?(:process),
"Block was not used with text line")
assert_equal("YAYNESS", record.process("yayness")[:line],
"Did not call process method")
end
def test_hooks
record = nil
# First try it with a normal record
assert_nothing_raised("Could not set hooks") do
record = @parser.record_line :yay, :fields => %w{one two},
:post_parse => proc { |hash| hash[:posted] = true },
:pre_gen => proc { |hash| hash[:one] = hash[:one].upcase }
end
assert(record.respond_to?(:post_parse), "did not create method for post-hook")
assert(record.respond_to?(:pre_gen), "did not create method for pre-hook")
result = nil
assert_nothing_raised("Could not process line with hooks") do
result = @parser.parse_line("one two")
end
assert(result[:posted], "Did not run post-hook")
old_result = result
# Now make sure our pre-gen hook is called
assert_nothing_raised("Could not generate line with hooks") do
result = @parser.to_line(result)
end
assert_equal("ONE two", result, "did not call pre-gen hook")
assert_equal("one", old_result[:one], "passed original hash to pre hook")
end
end
class TestUtilFileRecord < Test::Unit::TestCase
include PuppetTest
include PuppetTest::FileParsing
Record = Puppet::Util::FileParsing::FileRecord
def test_new_filerecord
[ [:fake, {}],
[nil, ]
].each do |args|
assert_raise(ArgumentError, "Did not fail on %s" % args.inspect) do
Record.new(*args)
end
end
# Make sure the fields get turned into symbols
record = nil
assert_nothing_raised do
record = Record.new(:record, :fields => %w{one two})
end
assert_equal([:one, :two], record.fields,
"Did not symbolize fields")
# Make sure we fail on invalid fields
[:record_type, :target, :on_disk].each do |field|
assert_raise(ArgumentError, "Did not fail on invalid field %s" % field) {
Record.new(:record, :fields => [field])
}
end
end
def test_defaults
record = Record.new(:text, :match => %r{^#})
[:absent, :separator, :joiner, :optional].each do |field|
assert_nil(record.send(field), "%s was not nil" % field)
end
record = Record.new(:record, :fields => %w{fields})
{:absent => "", :separator => /\s+/, :joiner => " ",
:optional => []}.each do |field, default|
assert_equal(default, record.send(field), "%s was not default" % field)
end
end
def test_block
record = nil
assert_nothing_raised("Could not pass a block when creating record") do
record = Record.new(:record, :fields => %w{one}) do |line|
return line.upcase
end
end
line = "This is a line"
assert(record.respond_to?(:process),
"Record did not define :process method")
assert_equal(line.upcase, record.process(line),
"Record did not process line correctly")
end
end
# $Id$