diff --git a/lib/puppet/event.rb b/lib/puppet/event.rb index 307c3540a..98ea92c9d 100644 --- a/lib/puppet/event.rb +++ b/lib/puppet/event.rb @@ -1,264 +1,31 @@ require 'puppet' require 'puppet/type' module Puppet # events are transient packets of information; they result in one or more (or none) # subscriptions getting triggered, and then they get cleared # eventually, these will be passed on to some central event system class Event include Puppet - - # subscriptions are permanent associations determining how different - # objects react to an event - class Subscription - include Puppet - attr_accessor :event, :callback - - # Remove the existing subscriptions and such - def self.clear - self.init - end - - # Remove a subscription - def self.delete(sub) - type, name = sub.targetarray - if @dependencies[type][name].include?(sub) - @dependencies[type][name].delete(sub) - end - - type, name = sub.sourcearray - if @subscriptions[type][name].include?(sub) - @subscriptions[type][name].delete(sub) - end - end - - # Initialize our class variables. This is in a method so it can - # be called to clear the variables, too. - def self.init - # A hash of subscriptions and another of dependencies, organized by - # type, then by name. I'm storing them all here, so that I don't - # have to store the subscriptions with the individual objects, - # which makes creating and destroying objects as necessary much - # easier. - @subscriptions = Hash.new { |hash, key| - hash[key] = Hash.new { |shash, skey| - # Each object has an array of subscriptions - shash[skey] = [] - } - } - - @dependencies = Hash.new { |hash, key| - hash[key] = Hash.new { |shash, skey| - # Each object has an array of subscriptions - shash[skey] = [] - } - } - end - - self.init - - # Store the new subscription in a central hash. - def self.newsub(sub) - # The dependencies map allows me to look up a subscription by - # target -- find out which objects a given object is subscribed - # to, and thus find out which objects that given object depends - # upon. - # DEPENDENCIES == TARGET - ttype, tname = sub.targetarray - @dependencies[ttype][tname] << sub - - # Subscriptions are the list of subscriptions for a given object, - # i.e., the list of all objects that care about a given object's - # events. - # SUBSCRIPTION == SOURCE - stype, sname = sub.sourcearray - @subscriptions[stype][sname] << sub - end - - # Collect all of the subscriptions that target a specific event. - def self.targets_of(event, source) - type, name = self.split(source) - - @subscriptions[type][name].each { |sub| - if sub.match?(event) - yield sub - end - } - end - - # Trigger the subscriptions related to an event, and then pass it up - # as appropriate - def self.trigger(source, event) - type, name = self.split(source) - - @subscriptions[type][name].each { |sub| - if sub.match?(event) - yield sub - #sub.trigger(transaction) - end - } - end - - # Look up an object by type and name. This is used because we - # store symbolic links in our subscription hash rather than storing - # actual object references. - def self.retrieve(ary) - type, name = ary - typeobj = Puppet::Type.type(type) - - unless typeobj - return nil - end - - obj = typeobj[name] - return obj - end - - # Split an object into its type and name - def self.split(object) - return [object.class.name, object.title] - end - - # Retrieve all of the subscriptions that result in a dependency. - # We return the whole dependency here, because it is being returned - # to the object that made the subscription. - def self.dependencies(target) - type, name = self.split(target) - return @dependencies[type][name] - end - - # Return all objects that are subscribed to us. We are only willing - # to return the object, not the subscription object, because the - # source shouldn't need to know things like the event or method that - # we're subscribed to. - def self.subscribers(source) - type, name = self.split(source) - return @subscriptions[type][name].collect { |sub| - sub.target - }.reject { |o| - o.nil? - } - end - - def delete - self.class.delete(self) - end - - # The hash here must include the target and source objects, the event, - # and the callback to call. - def initialize(hash) - hash.each { |param,value| - # assign each value appropriately - # this is probably wicked-slow - self.send(param.to_s + "=",value) - } - - self.class.newsub(self) - #Puppet.debug "New Subscription: '%s' => '%s'" % - # [@source,@event] - end - - # Determine whether the passed event matches our event - def match?(event) - if event == :NONE or @event == :NONE - return false - elsif @event == :ALL_EVENTS or event == :ALL_EVENTS or event == @event - return true - else - return false - end - end - - # The source is the event source. - def source=(object) - type, name = self.class.split(object) - @source = [type, name] - end - - def source - if source = self.class.retrieve(@source) - return source - else - raise Puppet::Error, - "Could not retrieve dependency %s[%s] for %s[%s]" % - [@source[0], @source[1], @target[0], @target[1]] - end - end - - def sourcearray - @source - end - - # The target is the object who will receive the callbacks, i.e., - # a source generates an event, which results in a callback on the - # target. - def target=(object) - type, name = self.class.split(object) - @target = [type, name] - end - - def target - self.class.retrieve(@target) - end - - def targetarray - @target - end - - # Trigger a subscription, which basically calls the associated method - # on the target object. XXX This is currently unused. - def trigger(transaction) - event = nil - - if @event == :NONE - # just ignore these subscriptions - return - end - - if transaction.triggered?(self.target, @callback) > 0 - self.target.info "already applied %s" % [@callback] - else - # We need to call the method, so that it gets retrieved - # as a real object. - target = self.target - #Puppet.debug "'%s' matched '%s'; triggering '%s' on '%s'" % - # [@source,@event,@method,target] - if target.respond_to?(@callback) - target.log "triggering %s" % @callback - event = target.send(@callback) - else - Puppet.debug( - "'%s' of type '%s' does not respond to '%s'" % - [target,target.class,@callback.inspect] - ) - end - transaction.triggered(target, @callback) - end - return event - end - end - attr_accessor :event, :source, :transaction @@events = [] - @@subscriptions = [] - def initialize(args) unless args.include?(:event) and args.include?(:source) raise Puppet::DevError, "Event.new called incorrectly" end @change = args[:change] @event = args[:event] @source = args[:source] @transaction = args[:transaction] end def to_s @source.to_s + " -> " + self.event.to_s end end end # $Id$ diff --git a/lib/puppet/metatype/manager.rb b/lib/puppet/metatype/manager.rb index bad41570a..53cb5951d 100644 --- a/lib/puppet/metatype/manager.rb +++ b/lib/puppet/metatype/manager.rb @@ -1,157 +1,156 @@ require 'puppet' require 'puppet/util/classgen' # Methods dealing with Type management. This module gets included into the # Puppet::Type class, it's just split out here for clarity. module Puppet::MetaType module Manager include Puppet::Util::ClassGen # remove all type instances; this is mostly only useful for testing def allclear - Puppet::Event::Subscription.clear @types.each { |name, type| type.clear } end # iterate across all of the subclasses of Type def eachtype @types.each do |name, type| # Only consider types that have names #if ! type.parameters.empty? or ! type.validstates.empty? yield type #end end end # Load all types. Only currently used for documentation. def loadall typeloader.loadall end # Do an on-demand plugin load def loadplugin(name) paths = Puppet[:pluginpath].split(":") unless paths.include?(Puppet[:plugindest]) Puppet.notice "Adding plugin destination %s to plugin search path" % Puppet[:plugindest] Puppet[:pluginpath] += ":" + Puppet[:plugindest] end paths.each do |dir| file = ::File.join(dir, name.to_s + ".rb") if FileTest.exists?(file) begin load file Puppet.info "loaded %s" % file return true rescue LoadError => detail Puppet.info "Could not load plugin %s: %s" % [file, detail] return false end end end end # Define a new type. def newtype(name, parent = nil, &block) # First make sure we don't have a method sitting around name = symbolize(name) newmethod = "new#{name.to_s}" # Used for method manipulation. selfobj = metaclass() @types ||= {} if @types.include?(name) if self.respond_to?(newmethod) # Remove the old newmethod selfobj.send(:remove_method,newmethod) end end # Then create the class. klass = genclass(name, :parent => (parent || Puppet::Type), :overwrite => true, :hash => @types, &block ) # Now define a "new" method for convenience. if self.respond_to? newmethod # Refuse to overwrite existing methods like 'newparam' or 'newtype'. Puppet.warning "'new#{name.to_s}' method already exists; skipping" else selfobj.send(:define_method, newmethod) do |*args| klass.create(*args) end end # If they've got all the necessary methods defined and they haven't # already added the state, then do so now. if klass.ensurable? and ! klass.validstate?(:ensure) klass.ensurable end # Now set up autoload any providers that might exist for this type. klass.providerloader = Puppet::Autoload.new(klass, "puppet/provider/#{klass.name.to_s}" ) # We have to load everything so that we can figure out the default type. klass.providerloader.loadall() klass end # Remove an existing defined type. Largely used for testing. def rmtype(name) # Then create the class. klass = rmclass(name, :hash => @types ) if respond_to?("new" + name.to_s) metaclass.send(:remove_method, "new" + name.to_s) end end # Return a Type instance by name. def type(name) @types ||= {} name = symbolize(name) if t = @types[name] return t else if typeloader.load(name) unless @types.include? name Puppet.warning "Loaded puppet/type/#{name} but no class was created" end else # If we can't load it from there, try loading it as a plugin. loadplugin(name) end return @types[name] end end # Create a loader for Puppet types. def typeloader unless defined? @typeloader @typeloader = Puppet::Autoload.new(self, "puppet/type", :wrap => false ) end @typeloader end end end # $Id$ diff --git a/lib/puppet/provider/nameservice/netinfo.rb b/lib/puppet/provider/nameservice/netinfo.rb index 879bb7757..313b41753 100644 --- a/lib/puppet/provider/nameservice/netinfo.rb +++ b/lib/puppet/provider/nameservice/netinfo.rb @@ -1,208 +1,211 @@ # Manage NetInfo POSIX objects. Probably only used on OS X, but I suppose # it could be used elsewhere. require 'puppet' require 'puppet/provider/nameservice' class Puppet::Provider::NameService class NetInfo < Puppet::Provider::NameService class << self attr_writer :netinfodir end # Attempt to flush the database, but this doesn't seem to work at all. def self.flush begin output = execute("/usr/sbin/lookupd -flushcache 2>&1") rescue Puppet::ExecutionFailure # Don't throw an error; it's just a failed cache flush Puppet.err "Could not flush lookupd cache: %s" % output end end # Similar to posixmethod, what key do we use to get data? Defaults # to being the object name. def self.netinfodir if defined? @netinfodir return @netinfodir else return @model.name.to_s + "s" end end def self.finish case self.name when :uid: noautogen when :gid: noautogen end end # Convert a NetInfo line into a hash of data. def self.line2hash(line, params) values = line.split(/\t/) hash = {} params.zip(values).each do |param, value| next if value == '#NoValue#' hash[param] = if value =~ /^[-0-9]+$/ Integer(value) else value end end hash end def self.list report(@model.validstates).collect do |hash| @model.hash2obj(hash) end end # What field the value is stored under. def self.netinfokey(name) name = symbolize(name) self.option(name, :key) || name end # Retrieve the data, yo. # FIXME This should retrieve as much information as possible, # rather than retrieving it one at a time. def self.report(*params) dir = self.netinfodir() cmd = [command(:nireport), "/", "/%s" % dir] # We require the name in order to know if we match. There's no # way to just report on our individual object, we have to get the # whole list. params.unshift :name unless params.include? :name params.each do |param| if key = netinfokey(param) cmd << key.to_s else raise Puppet::DevError, "Could not find netinfokey for state %s" % self.class.name end end begin output = execute(cmd.join(" ")) rescue Puppet::ExecutionFailure => detail Puppet.err "Failed to call nireport: %s" % detail return nil end return output.split("\n").collect { |line| line2hash(line, params) } end # How to add an object. def addcmd creatorcmd("-create") end def creatorcmd(arg) cmd = [command(:niutil)] cmd << arg cmd << "/" << "/%s/%s" % [self.class.netinfodir(), @model[:name]] return cmd.join(" ") end def deletecmd creatorcmd("-destroy") end def destroy delete() end def ensure=(arg) super # Because our stupid type can't create the whole thing at once, # we have to do this hackishness. Yay. if arg == :present # We need to generate the id if it's missing. @model.class.validstates.each do |name| next if name == :ensure unless val = @model.should(name) if (@model.class.name == :user and name == :uid) or (@model.class.name == :group and name == :gid) val = autogen() else # No value, and it's not required, so skip it. next end end self.send(name.to_s + "=", val) end end end # Retrieve a specific value by name. def get(param) hash = getinfo(false) if hash return hash[param] else return :absent end end # Retrieve everything about this object at once, instead of separately. def getinfo(refresh = false) if refresh or (! defined? @infohash or ! @infohash) states = [:name] + self.class.model.validstates states.delete(:ensure) if states.include? :ensure @infohash = single_report(*states) end return @infohash end def modifycmd(param, value) cmd = [command(:niutil)] + # if value.is_a?(Array) + # warning "Netinfo providers cannot currently handle multiple values" + # end cmd << "-createprop" << "/" << "/%s/%s" % [self.class.netinfodir, @model[:name]] if key = netinfokey(param) cmd << key << "'%s'" % value else raise Puppet::DevError, "Could not find netinfokey for state %s" % self.class.name end cmd.join(" ") end # Determine the flag to pass to our command. def netinfokey(name) self.class.netinfokey(name) end # Get a report for a single resource, not the whole table def single_report(*states) self.class.report(*states).find do |hash| hash[:name] == @model[:name] end end def setuserlist(group, list) cmd = "#{command(:niutil)} -createprop / /groups/%s users %s" % [group, list.join(",")] begin output = execute(cmd) rescue Puppet::Execution::Failure => detail raise Puppet::Error, "Failed to set user list on %s: %s" % [group, detail] end end end end # $Id$ diff --git a/lib/puppet/type/state.rb b/lib/puppet/type/state.rb index 6dd5f1567..24dec6903 100644 --- a/lib/puppet/type/state.rb +++ b/lib/puppet/type/state.rb @@ -1,545 +1,547 @@ # The virtual base class for states, which are the self-contained building # blocks for actually doing work on the system. require 'puppet' require 'puppet/element' require 'puppet/statechange' require 'puppet/parameter' module Puppet class State < Puppet::Parameter attr_accessor :is # Because 'should' uses an array, we have a special method for handling # it. We also want to keep copies of the original values, so that # they can be retrieved and compared later when merging. attr_reader :shouldorig class << self attr_accessor :unmanaged attr_reader :name def checkable @checkable = true end def uncheckable @checkable = false end def checkable? if defined? @checkable return @checkable else return true end end end # Look up a value's name, so we can find options and such. def self.value_name(value) name = symbolize(value) if @parametervalues[name] return name elsif ary = self.match?(value) return ary[0] else return nil end end # Retrieve an option set when a value was defined. def self.value_option(name, option) if option.is_a?(String) option = symbolize(option) end if hash = @parameteroptions[name] hash[option] else nil end end # Create the value management variables. def self.initvars @parametervalues = {} @aliasvalues = {} @parameterregexes = {} @parameteroptions = {} end # Define a new valid value for a state. You must provide the value itself, # usually as a symbol, or a regex to match the value. # # The first argument to the method is either the value itself or a regex. # The second argument is an option hash; valid options are: # * :event: The event that should be returned when this value is set. # * :call: When to call any associated block. The default value # is ``instead``, which means to call the value instead of calling the # provider. You can also specify ``before`` or ``after``, which will # call both the block and the provider, according to the order you specify # (the ``first`` refers to when the block is called, not the provider). def self.newvalue(name, options = {}, &block) name = name.intern if name.is_a? String @parameteroptions[name] = {} paramopts = @parameteroptions[name] # Symbolize everything options.each do |opt, val| paramopts[symbolize(opt)] = symbolize(val) end # By default, call the block instead of the provider. if block_given? paramopts[:call] ||= :instead else paramopts[:call] ||= :none end # If there was no block given, we still want to store the information # for validation, but we won't be defining a method block ||= true case name when Symbol if @parametervalues.include?(name) Puppet.warning "%s reassigning value %s" % [self.name, name] end @parametervalues[name] = block if block_given? method = "set_" + name.to_s settor = paramopts[:settor] || (self.name.to_s + "=") define_method(method, &block) paramopts[:method] = method end when Regexp # The regexes are handled in parameter.rb. This value is used # for validation. @parameterregexes[name] = block # This is used for looking up the block for execution. if block_given? paramopts[:block] = block end else raise ArgumentError, "Invalid value %s of type %s" % [name, name.class] end end # Call the provider method. def call_provider(value) begin provider.send(self.class.name.to_s + "=", value) rescue NoMethodError self.fail "The %s provider can not handle attribute %s" % [provider.class.name, self.class.name] end end # Call the dynamically-created method associated with our value, if # there is one. def call_valuemethod(name, value) event = nil if method = self.class.value_option(name, :method) and self.respond_to?(method) self.debug "setting %s (currently %s)" % [value, self.is] begin event = self.send(method) rescue Puppet::Error raise rescue => detail if Puppet[:trace] puts detail.backtrace end error = Puppet::Error.new("Could not set %s on %s: %s" % [value, self.class.name, detail], @parent.line, @parent.file) error.set_backtrace detail.backtrace raise error end elsif block = self.class.value_option(name, :block) # FIXME It'd be better here to define a method, so that # the blocks could return values. # If the regex was defined with no associated block, then just pass # through and the correct event will be passed back. event = self.instance_eval(&block) end return event, name end # How should a state change be printed as a string? def change_to_s begin if @is == :absent return "defined '%s' as '%s'" % [self.name, self.should_to_s] elsif self.should == :absent or self.should == [:absent] return "undefined %s from '%s'" % [self.name, self.is_to_s] else return "%s changed '%s' to '%s'" % [self.name, self.is_to_s, self.should_to_s] end rescue Puppet::Error, Puppet::DevError raise rescue => detail raise Puppet::DevError, "Could not convert change %s to string: %s" % [self.name, detail] end end # Figure out which event to return. def event(name, event = nil) if value_event = self.class.value_option(name, :event) return value_event else if event and event.is_a?(Symbol) if event == :nochange return nil else return event end else event = case self.should when :present: (@parent.class.name.to_s + "_created").intern when :absent: (@parent.class.name.to_s + "_removed").intern else (@parent.class.name.to_s + "_changed").intern end end end return event end # initialize our state def initialize(hash) super() @is = nil unless hash.include?(:parent) self.devfail "State %s was not passed a parent" % self end @parent = hash[:parent] if hash.include?(:should) self.should = hash[:should] end if hash.include?(:is) self.is = hash[:is] end end def inspect str = "State('%s', " % self.name if self.is str += "@is = '%s', " % [self.is] else str += "@is = nil, " end if defined? @should and @should str += "@should = '%s')" % @should.join(", ") else str += "@should = nil)" end end # Determine whether the state is in-sync or not. If @should is # not defined or is set to a non-true value, then we do not have # a valid value for it and thus consider the state to be in-sync # since we cannot fix it. Otherwise, we expect our should value # to be an array, and if @is matches any of those values, then # we consider it to be in-sync. def insync? #debug "%s value is '%s', should be '%s'" % # [self,self.is.inspect,self.should.inspect] unless defined? @should and @should return true end unless @should.is_a?(Array) self.devfail "%s's should is not array" % self.class.name end # an empty array is analogous to no should values if @should.empty? return true end # Look for a matching value @should.each { |val| if @is == val or @is == val.to_s return true end } # otherwise, return false return false end # because the @should and @is vars might be in weird formats, # we need to set up a mechanism for pretty printing of the values # default to just the values, but this way individual states can # override these methods def is_to_s @is end # Send a log message. def log(msg) unless @parent[:loglevel] self.devfail "Parent %s has no loglevel" % @parent.name end Puppet::Log.create( :level => @parent[:loglevel], :message => msg, :source => self ) end # each state class must define the name() method, and state instances # do not change that name # this implicitly means that a given object can only have one state # instance of a given state class def name return self.class.name end # for testing whether we should actually do anything def noop unless defined? @noop @noop = false end tmp = @noop || self.parent.noop || Puppet[:noop] || false #debug "noop is %s" % tmp return tmp end # return the full path to us, for logging and rollback; not currently # used def pathbuilder if defined? @parent and @parent return [@parent.path, self.name] else return [self.name] end end # Retrieve the parent's provider. Some types don't have providers, in which # case we return the parent object itself. def provider @parent.provider || @parent end # By default, call the method associated with the state name on our # provider. In other words, if the state name is 'gid', we'll call # 'provider.gid' to retrieve the current value. def retrieve @is = provider.send(self.class.name) end # Set our value, using the provider, an associated block, or both. def set(value) # Set a name for looking up associated options like the event. name = self.class.value_name(value) call = self.class.value_option(name, :call) # If we're supposed to call the block first or instead, call it now if call == :before or call == :instead event, tmp = call_valuemethod(name, value) end unless call == :instead if @parent.provider call_provider(value) else # They haven't provided a block, and our parent does not have # a provider, so we have no idea how to handle this. self.fail "%s cannot handle values of type %s" % [self.class.name, value.inspect] end end if call == :after event, tmp = call_valuemethod(name, value) end return event(name, event) end # Only return the first value def should if defined? @should unless @should.is_a?(Array) self.devfail "should for %s on %s is not an array" % [self.class.name, @parent.name] end return @should[0] else return nil end end # Set the should value. def should=(values) unless values.is_a?(Array) values = [values] end @shouldorig = values if self.respond_to?(:validate) values.each { |val| validate(val) } end if self.respond_to?(:munge) @should = values.collect { |val| self.munge(val) } else @should = values end end def should_to_s if defined? @should @should.join(" ") else return nil end end # The default 'sync' method only selects among a list of registered # values. def sync if self.insync? self.info "already in sync" return nil end unless self.class.values self.devfail "No values defined for %s" % self.class.name end if value = self.should set(value) else self.devfail "Got a nil value for should" end end # The states need to return tags so that logs correctly collect them. def tags unless defined? @tags @tags = [] # This might not be true in testing if @parent.respond_to? :tags @tags = @parent.tags end @tags << self.name end @tags end def to_s return "%s(%s)" % [@parent.name,self.name] end # This state will get automatically added to any type that responds # to the methods 'exists?', 'create', and 'destroy'. class Ensure < Puppet::State @name = :ensure def self.defaultvalues newvalue(:present) do if @parent.provider and @parent.provider.respond_to?(:create) @parent.provider.create else @parent.create end + nil # return nil so the event is autogenerated end newvalue(:absent) do if @parent.provider and @parent.provider.respond_to?(:destroy) @parent.provider.destroy else @parent.destroy end + nil # return nil so the event is autogenerated end defaultto do if @parent.managed? :present else nil end end # This doc will probably get overridden @doc ||= "The basic state that the object should be in." end def self.inherited(sub) # Add in the two states that everyone will have. sub.class_eval do end end def change_to_s begin if @is == :absent or @is.nil? return "created" elsif self.should == :absent return "removed" else return "%s changed '%s' to '%s'" % [self.name, self.is_to_s, self.should_to_s] end rescue Puppet::Error, Puppet::DevError raise rescue => detail raise Puppet::DevError, "Could not convert change %s to string: %s" % [self.name, detail] end end def retrieve # XXX This is a problem -- whether the object exists or not often # depends on the results of other states, yet we're the first state # to get checked, which means that those other states do not have # @is values set. This seems to be the source of quite a few bugs, # although they're mostly logging bugs, not functional ones. if prov = @parent.provider and prov.respond_to?(:exists?) result = prov.exists? elsif @parent.respond_to?(:exists?) result = @parent.exists? else raise Puppet::DevError, "No ability to determine if %s exists" % @parent.class.name end if result @is = :present else @is = :absent end end # If they're talking about the thing at all, they generally want to # say it should exist. #defaultto :present defaultto do if @parent.managed? :present else nil end end end end end # $Id$ diff --git a/test/other/events.rb b/test/other/events.rb index 078e6351f..802a701a3 100755 --- a/test/other/events.rb +++ b/test/other/events.rb @@ -1,170 +1,166 @@ #!/usr/bin/env ruby $:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ require 'puppet' require 'puppettest' # $Id$ class TestEvents < Test::Unit::TestCase include PuppetTest - def teardown - super - Puppet::Event::Subscription.clear - end def test_simplesubscribe name = tempfile() file = Puppet.type(:file).create( :name => name, :ensure => "file" ) exec = Puppet.type(:exec).create( :name => "echo true", :path => "/usr/bin:/bin", :refreshonly => true, :subscribe => [[file.class.name, file.name]] ) comp = newcomp("eventtesting", file, exec) trans = assert_events([:file_created, :triggered], comp) assert_equal(1, trans.triggered?(exec, :refresh)) end def test_simplerequire name = tempfile() file = Puppet.type(:file).create( :name => name, :ensure => "file" ) exec = Puppet.type(:exec).create( :name => "echo true", :path => "/usr/bin:/bin", :refreshonly => true, :require => [[file.class.name, file.name]] ) comp = Puppet.type(:component).create( :name => "eventtesting" ) comp.push exec trans = comp.evaluate events = nil assert_nothing_raised { events = trans.evaluate } assert_equal(1, events.length) assert_equal(0, trans.triggered?(exec, :refresh)) end # Verify that one component can subscribe to another component and the "right" # thing happens def test_ladderrequire comps = {} objects = {} fname = tempfile() file = Puppet.type(:file).create( :name => tempfile(), :ensure => "file" ) exec = Puppet.type(:exec).create( :name => "touch %s" % fname, :path => "/usr/bin:/bin", :refreshonly => true ) fcomp = newcomp(file) ecomp = newcomp(exec) comp = newcomp("laddercomp", fcomp, ecomp) ecomp[:subscribe] = [[fcomp.class.name, fcomp.name]] comp.finalize trans = comp.evaluate events = nil assert_nothing_raised { events = trans.evaluate } assert(FileTest.exists?(fname), "#{fname} does not exist") #assert_equal(events.length, trans.triggered?(objects[:b], :refresh)) end def test_multiplerefreshes files = [] 4.times { |i| files << Puppet.type(:file).create( :name => tempfile(), :ensure => "file" ) } fname = tempfile() exec = Puppet.type(:exec).create( :name => "touch %s" % fname, :path => "/usr/bin:/bin", :refreshonly => true ) exec[:subscribe] = files.collect { |f| ["file", f.name] } comp = newcomp(exec, *files) assert_apply(comp) assert(FileTest.exists?(fname), "Exec file did not get created") end # Make sure refreshing happens mid-transaction, rather than at the end. def test_refreshordering file = tempfile() exec1 = Puppet.type(:exec).create( :title => "one", :name => "echo one >> %s" % file, :path => "/usr/bin:/bin" ) exec2 = Puppet.type(:exec).create( :title => "two", :name => "echo two >> %s" % file, :path => "/usr/bin:/bin", :refreshonly => true, :subscribe => exec1 ) exec3 = Puppet.type(:exec).create( :title => "three", :name => "echo three >> %s" % file, :path => "/usr/bin:/bin", :require => exec2 ) execs = [exec1, exec2, exec3] comp = newcomp(exec1,exec2,exec3) trans = comp.evaluate execs.each do |e| assert(trans.resources.vertex?(e), "%s is not in graph" % e.title) end trans.prepare execs.each do |e| assert(trans.relgraph.vertex?(e), "%s is not in relgraph" % e.title) end reverse = trans.relgraph.reversal execs.each do |e| assert(reverse.vertex?(e), "%s is not in reversed graph" % e.title) end assert_apply(comp) assert(FileTest.exists?(file), "File does not exist") assert_equal("one\ntwo\nthree\n", File.read(file)) end end diff --git a/test/other/relationships.rb b/test/other/relationships.rb index 164d52d2a..7b321d821 100755 --- a/test/other/relationships.rb +++ b/test/other/relationships.rb @@ -1,266 +1,211 @@ #!/usr/bin/env ruby $:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ require 'puppet' require 'puppettest' class TestRelationships < Test::Unit::TestCase include PuppetTest + def setup + super + Puppet::Type.type(:exec) + end + def newfile assert_nothing_raised() { return Puppet.type(:file).create( :path => tempfile, :check => [:mode, :owner, :group] ) } end def check_relationship(sources, targets, out, refresher) if out deps = sources.builddepends sources = [sources] else deps = targets.builddepends targets = [targets] end assert_instance_of(Array, deps) assert(! deps.empty?, "Did not receive any relationships") deps.each do |edge| assert_instance_of(Puppet::Relationship, edge) end sources.each do |source| targets.each do |target| edge = deps.find { |e| e.source == source and e.target == target } assert(edge, "Could not find edge for %s => %s" % [source.ref, target.ref]) if refresher assert_equal(:ALL_EVENTS, edge.event) assert_equal(:refresh, edge.callback) else assert_equal(:NONE, edge.event) assert_nil(edge.callback, "Got a callback with no events") end end end end # Make sure our various metaparams work correctly. We're just checking # here whether they correctly set up the callbacks and the direction of # the relationship. def test_relationship_metaparams out = {:require => false, :subscribe => false, :notify => true, :before => true} refreshers = [:subscribe, :notify] [:require, :subscribe, :notify, :before].each do |param| # Create three files to generate our events and three # execs to receive them files = [] execs = [] 3.times do |i| files << Puppet::Type.newfile( :title => "file#{i}", :path => tempfile(), :ensure => :file ) path = tempfile() execs << Puppet::Type.newexec( :title => "notifytest#{i}", :path => "/usr/bin:/bin", :command => "touch #{path}", :refreshonly => true ) end # Add our first relationship if out[param] files[0][param] = execs[0] sources = files[0] targets = [execs[0]] else execs[0][param] = files[0] sources = [files[0]] targets = execs[0] end check_relationship(sources, targets, out[param], refreshers.include?(param)) # Now add another relationship if out[param] files[0][param] = execs[1] targets << execs[1] assert_equal(targets.collect { |t| [t.class.name, t.title]}, files[0][param], "Incorrect target list") else execs[0][param] = files[1] sources << files[1] assert_equal(sources.collect { |t| [t.class.name, t.title]}, execs[0][param], "Incorrect source list") end check_relationship(sources, targets, out[param], refreshers.include?(param)) Puppet::Type.allclear end end def test_store_relationship file = Puppet::Type.newfile :path => tempfile(), :mode => 0755 execs = [] 3.times do |i| execs << Puppet::Type.newexec(:title => "yay#{i}", :command => "/bin/echo yay") end # First try it with one object, specified as a reference and an array result = nil [execs[0], [:exec, "yay0"], ["exec", "yay0"]].each do |target| assert_nothing_raised do result = file.send(:store_relationship, :require, target) end assert_equal([[:exec, "yay0"]], result) end # Now try it with multiple objects symbols = execs.collect { |e| [e.class.name, e.title] } strings = execs.collect { |e| [e.class.name.to_s, e.title] } [execs, symbols, strings].each do |target| assert_nothing_raised do result = file.send(:store_relationship, :require, target) end assert_equal(symbols, result) end # Make sure we can mix it up, even though this shouldn't happen assert_nothing_raised do result = file.send(:store_relationship, :require, [execs[0], [execs[1].class.name, execs[1].title]]) end assert_equal([[:exec, "yay0"], [:exec, "yay1"]], result) # Finally, make sure that new results get added to old. The only way # to get rid of relationships is to delete the parameter. file[:require] = execs[0] assert_nothing_raised do result = file.send(:store_relationship, :require, [execs[1], execs[2]]) end assert_equal(symbols, result) end - - def test_newsub - file1 = newfile() - file2 = newfile() - - sub = nil - assert_nothing_raised("Could not create subscription") { - sub = Puppet::Event::Subscription.new( - :source => file1, - :target => file2, - :event => :ALL_EVENTS, - :callback => :refresh - ) - } - - subs = nil - - assert_nothing_raised { - subs = Puppet::Event::Subscription.subscribers(file1) - } - assert_equal(1, subs.length, "Got incorrect number of subs") - assert_equal(sub.target, subs[0], "Got incorrect sub") - - deps = nil - assert_nothing_raised { - deps = Puppet::Event::Subscription.dependencies(file2) - } - assert_equal(1, deps.length, "Got incorrect number of deps") - assert_equal(sub, deps[0], "Got incorrect dep") - end - - def test_eventmatch - file1 = newfile() - file2 = newfile() - - sub = nil - assert_nothing_raised("Could not create subscription") { - sub = Puppet::Event::Subscription.new( - :source => file1, - :target => file2, - :event => :ALL_EVENTS, - :callback => :refresh - ) - } - - assert(sub.match?(:anything), "ALL_EVENTS did not match") - assert(! sub.match?(:NONE), "ALL_EVENTS matched :NONE") - - sub.event = :file_created - - assert(sub.match?(:file_created), "event did not match") - assert(sub.match?(:ALL_EVENTS), "ALL_EVENTS did not match") - assert(! sub.match?(:NONE), "ALL_EVENTS matched :NONE") - - sub.event = :NONE - - assert(! sub.match?(:file_created), "Invalid match") - assert(! sub.match?(:ALL_EVENTS), "ALL_EVENTS matched") - assert(! sub.match?(:NONE), "matched :NONE") - end def test_autorequire # We know that execs autorequire their cwd, so we'll use that path = tempfile() file = Puppet::Type.newfile(:title => "myfile", :path => path, :ensure => :directory) exec = Puppet::Type.newexec(:title => "myexec", :cwd => path, :command => "/bin/echo") reqs = nil assert_nothing_raised do reqs = exec.autorequire end assert_equal([Puppet::Relationship[file, exec]], reqs) # Now make sure that these relationships are added to the transaction's # relgraph trans = Puppet::Transaction.new(newcomp(file, exec)) assert_nothing_raised do trans.evaluate end graph = trans.relgraph assert(graph.edge?(file, exec), "autorequire edge was not created") end def test_requires? # Test the first direction file1 = Puppet::Type.newfile(:title => "one", :path => tempfile, :ensure => :directory) file2 = Puppet::Type.newfile(:title => "two", :path => tempfile, :ensure => :directory) file1[:require] = file2 assert(file1.requires?(file2), "requires? failed to catch :require relationship") file1.delete(:require) assert(! file1.requires?(file2), "did not delete relationship") file1[:subscribe] = file2 assert(file1.requires?(file2), "requires? failed to catch :subscribe relationship") file1.delete(:subscribe) assert(! file1.requires?(file2), "did not delete relationship") file2[:before] = file1 assert(file1.requires?(file2), "requires? failed to catch :before relationship") file2.delete(:before) assert(! file1.requires?(file2), "did not delete relationship") file2[:notify] = file1 assert(file1.requires?(file2), "requires? failed to catch :notify relationship") end end # $Id$ diff --git a/test/other/report.rb b/test/other/report.rb index 939d87aaf..551cf4b28 100755 --- a/test/other/report.rb +++ b/test/other/report.rb @@ -1,152 +1,155 @@ #!/usr/bin/env ruby $:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ require 'puppet' require 'puppet/transaction/report' require 'puppettest' require 'puppettest/reporttesting' class TestReports < Test::Unit::TestCase include PuppetTest include PuppetTest::Reporttesting # Make sure we can use reports as log destinations. def test_reports_as_log_destinations report = fakereport assert_nothing_raised { Puppet::Log.newdestination(report) } # Now make a file for testing logging file = Puppet::Type.newfile(:path => tempfile(), :ensure => "file") file.finish log = nil assert_nothing_raised { log = file.log "This is a message, yo" } assert(report.logs.include?(log), "Report did not get log message") log = Puppet.warning "This is a non-sourced message" assert(! report.logs.include?(log), "Report got log message") assert_nothing_raised { Puppet::Log.close(report) } log = file.log "This is another message, yo" assert(! report.logs.include?(log), "Report got log message after close") end def test_newmetric report = nil assert_nothing_raised { report = Puppet::Transaction::Report.new } assert_nothing_raised { report.newmetric(:mymetric, :total => 12, :done => 6 ) } end def test_store_report # Create a bunch of log messages in an array. report = Puppet::Transaction::Report.new + + # We have to reuse reporting here because of something going on in the server/report.rb file + Puppet.config.use(:reporting) 3.times { |i| log = Puppet.warning("Report test message %s" % i) log.tags = %w{a list of tags} log.tags << "tag%s" % i report.newlog(log) } assert_nothing_raised do report.extend(Puppet::Server::Report.report(:store)) end yaml = YAML.dump(report) file = nil assert_nothing_raised { file = report.process(yaml) } assert(FileTest.exists?(file), "report file did not get created") assert_equal(yaml, File.read(file), "File did not get written") end if Puppet.features.rrd? def test_rrdgraph_report Puppet.config.use(:metrics) # First do some work objects = [] 25.times do |i| file = tempfile() # Make every third file File.open(file, "w") { |f| f.puts "" } if i % 3 == 0 objects << Puppet::Type.newfile( :path => file, :ensure => "file" ) end comp = newcomp(*objects) trans = nil assert_nothing_raised("Failed to create transaction") { trans = comp.evaluate } assert_nothing_raised("Failed to evaluate transaction") { trans.evaluate } report = trans.report assert_nothing_raised do report.extend(Puppet::Server::Report.report(:rrdgraph)) end assert_nothing_raised { report.process } hostdir = nil assert_nothing_raised do hostdir = report.hostdir end assert(hostdir, "Did not get hostdir back") assert(FileTest.directory?(hostdir), "Host rrd dir did not get created") index = File.join(hostdir, "index.html") assert(FileTest.exists?(index), "index file was not created") # Now make sure it creaets each of the rrd files %w{changes resources time}.each do |type| file = File.join(hostdir, "%s.rrd" % type) assert(FileTest.exists?(file), "Did not create rrd file for %s" % type) daily = file.sub ".rrd", "-daily.png" assert(FileTest.exists?(daily), "Did not make daily graph for %s" % type) end end else $stderr.puts "Install RRD for metric reporting tests" end end # $Id$ diff --git a/test/other/transactions.rb b/test/other/transactions.rb index 256c815f7..d9a173986 100755 --- a/test/other/transactions.rb +++ b/test/other/transactions.rb @@ -1,786 +1,786 @@ #!/usr/bin/env ruby $:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ require 'puppet' require 'puppettest' require 'puppettest/support/resources' # $Id$ class TestTransactions < Test::Unit::TestCase include PuppetTest::FileTesting include PuppetTest::Support::Resources def mkgenerator(&block) # Create a bogus type that generates new instances with shorter type = Puppet::Type.newtype(:generator) do newparam(:name, :namevar => true) end if block type.class_eval(&block) end cleanup do Puppet::Type.rmtype(:generator) end return type end def test_reports path1 = tempfile() path2 = tempfile() objects = [] objects << Puppet::Type.newfile( :path => path1, :content => "yayness" ) objects << Puppet::Type.newfile( :path => path2, :content => "booness" ) trans = assert_events([:file_created, :file_created], *objects) report = nil assert_nothing_raised { report = trans.report } # First test the report logs assert(report.logs.length > 0, "Did not get any report logs") report.logs.each do |obj| assert_instance_of(Puppet::Log, obj) end # Then test the metrics metrics = report.metrics assert(metrics, "Did not get any metrics") assert(metrics.length > 0, "Did not get any metrics") assert(metrics.has_key?("resources"), "Did not get object metrics") assert(metrics.has_key?("changes"), "Did not get change metrics") metrics.each do |name, metric| assert_instance_of(Puppet::Metric, metric) end end def test_prefetch # Create a type just for testing prefetch name = :prefetchtesting $prefetched = false type = Puppet::Type.newtype(name) do newparam(:name) {} end cleanup do Puppet::Type.rmtype(name) end # Now create a provider type.provide(:prefetch) do def self.prefetch $prefetched = true end end # Now create an instance inst = type.create :name => "yay" # Create a transaction trans = Puppet::Transaction.new(newcomp(inst)) # Make sure prefetch works assert_nothing_raised do trans.prefetch end assert_equal(true, $prefetched, "type prefetch was not called") # Now make sure it gets called from within evaluate() $prefetched = false assert_nothing_raised do trans.evaluate end assert_equal(true, $prefetched, "evaluate did not call prefetch") end def test_refreshes_generate_events path = tempfile() firstpath = tempfile() secondpath = tempfile() file = Puppet::Type.newfile(:title => "file", :path => path, :content => "yayness") first = Puppet::Type.newexec(:title => "first", :command => "/bin/echo first > #{firstpath}", :subscribe => [:file, path], :refreshonly => true ) second = Puppet::Type.newexec(:title => "second", :command => "/bin/echo second > #{secondpath}", :subscribe => [:exec, "first"], :refreshonly => true ) assert_apply(file, first, second) assert(FileTest.exists?(secondpath), "Refresh did not generate an event") end unless %x{groups}.chomp.split(/ /).length > 1 $stderr.puts "You must be a member of more than one group to test transactions" else def ingroup(gid) require 'etc' begin group = Etc.getgrgid(gid) rescue => detail puts "Could not retrieve info for group %s: %s" % [gid, detail] return nil end return @groups.include?(group.name) end def setup super @groups = %x{groups}.chomp.split(/ /) unless @groups.length > 1 p @groups raise "You must be a member of more than one group to test this" end end def newfile(hash = {}) tmpfile = tempfile() File.open(tmpfile, "w") { |f| f.puts rand(100) } # XXX now, because os x apparently somehow allows me to make a file # owned by a group i'm not a member of, i have to verify that # the file i just created is owned by one of my groups # grrr unless ingroup(File.stat(tmpfile).gid) Puppet.info "Somehow created file in non-member group %s; fixing" % File.stat(tmpfile).gid require 'etc' firstgr = @groups[0] unless firstgr.is_a?(Integer) str = Etc.getgrnam(firstgr) firstgr = str.gid end File.chown(nil, firstgr, tmpfile) end hash[:name] = tmpfile assert_nothing_raised() { return Puppet.type(:file).create(hash) } end def newservice assert_nothing_raised() { return Puppet.type(:service).create( :name => "sleeper", :type => "init", :path => exampledir("root/etc/init.d"), :hasstatus => true, :check => [:ensure] ) } end def newexec(file) assert_nothing_raised() { return Puppet.type(:exec).create( :name => "touch %s" % file, :path => "/bin:/usr/bin:/sbin:/usr/sbin", :returns => 0 ) } end # modify a file and then roll the modifications back def test_filerollback transaction = nil file = newfile() states = {} check = [:group,:mode] file[:check] = check assert_nothing_raised() { file.retrieve } assert_nothing_raised() { check.each { |state| assert(file[state]) states[state] = file[state] } } component = newcomp("file",file) require 'etc' groupname = Etc.getgrgid(File.stat(file.name).gid).name assert_nothing_raised() { # Find a group that it's not set to group = @groups.find { |group| group != groupname } unless group raise "Could not find suitable group" end file[:group] = group file[:mode] = "755" } trans = assert_events([:file_changed, :file_changed], component) file.retrieve assert_rollback_events(trans, [:file_changed, :file_changed], "file") assert_nothing_raised() { file.retrieve } states.each { |state,value| assert_equal( value,file.is(state), "File %s remained %s" % [state, file.is(state)] ) } end # start a service, and then roll the modification back # Disabled, because it wasn't really worth the effort. def disabled_test_servicetrans transaction = nil service = newservice() component = newcomp("service",service) assert_nothing_raised() { service[:ensure] = 1 } service.retrieve assert(service.insync?, "Service did not start") system("ps -ef | grep ruby") trans = assert_events([:service_started], component) service.retrieve assert_rollback_events(trans, [:service_stopped], "service") end # test that services are correctly restarted and that work is done # in the right order def test_refreshing transaction = nil file = newfile() execfile = File.join(tmpdir(), "exectestingness") exec = newexec(execfile) states = {} check = [:group,:mode] file[:check] = check file[:group] = @groups[0] assert_apply(file) @@tmpfiles << execfile component = newcomp("both",file,exec) # 'subscribe' expects an array of arrays exec[:subscribe] = [[file.class.name,file.name]] exec[:refreshonly] = true assert_nothing_raised() { file.retrieve exec.retrieve } check.each { |state| states[state] = file[state] } assert_nothing_raised() { file[:mode] = "755" } trans = assert_events([:file_changed, :triggered], component) assert(FileTest.exists?(execfile), "Execfile does not exist") File.unlink(execfile) assert_nothing_raised() { file[:group] = @groups[1] } trans = assert_events([:file_changed, :triggered], component) assert(FileTest.exists?(execfile), "Execfile does not exist") end # Verify that one component requiring another causes the contained # resources in the requiring component to get refreshed. def test_refresh_across_two_components transaction = nil file = newfile() execfile = File.join(tmpdir(), "exectestingness2") @@tmpfiles << execfile exec = newexec(execfile) states = {} check = [:group,:mode] file[:check] = check file[:group] = @groups[0] assert_apply(file) fcomp = newcomp("file",file) ecomp = newcomp("exec",exec) component = newcomp("both",fcomp,ecomp) # 'subscribe' expects an array of arrays #component[:require] = [[file.class.name,file.name]] ecomp[:subscribe] = fcomp exec[:refreshonly] = true trans = assert_events([], component) assert_nothing_raised() { file[:group] = @groups[1] file[:mode] = "755" } trans = assert_events([:file_changed, :file_changed, :triggered], component) end # Make sure that multiple subscriptions get triggered. def test_multisubs path = tempfile() file1 = tempfile() file2 = tempfile() file = Puppet.type(:file).create( :path => path, :ensure => "file" ) exec1 = Puppet.type(:exec).create( :path => ENV["PATH"], :command => "touch %s" % file1, :refreshonly => true, :subscribe => [:file, path] ) exec2 = Puppet.type(:exec).create( :path => ENV["PATH"], :command => "touch %s" % file2, :refreshonly => true, :subscribe => [:file, path] ) assert_apply(file, exec1, exec2) assert(FileTest.exists?(file1), "File 1 did not get created") assert(FileTest.exists?(file2), "File 2 did not get created") end # Make sure that a failed trigger doesn't result in other events not # getting triggered. def test_failedrefreshes path = tempfile() newfile = tempfile() file = Puppet.type(:file).create( :path => path, :ensure => "file" ) svc = Puppet.type(:service).create( :name => "thisservicedoesnotexist", :subscribe => [:file, path] ) exec = Puppet.type(:exec).create( :path => ENV["PATH"], :command => "touch %s" % newfile, :logoutput => true, :refreshonly => true, :subscribe => [:file, path] ) assert_apply(file, svc, exec) assert(FileTest.exists?(path), "File did not get created") assert(FileTest.exists?(newfile), "Refresh file did not get created") end # Make sure that unscheduled and untagged objects still respond to events def test_unscheduled_and_untagged_response Puppet::Type.type(:schedule).mkdefaultschedules Puppet[:ignoreschedules] = false file = Puppet.type(:file).create( :name => tempfile(), :ensure => "file" ) fname = tempfile() exec = Puppet.type(:exec).create( :name => "touch %s" % fname, :path => "/usr/bin:/bin", :schedule => "monthly", :subscribe => ["file", file.name] ) comp = newcomp(file,exec) comp.finalize # Run it once assert_apply(comp) assert(FileTest.exists?(fname), "File did not get created") assert(!exec.scheduled?, "Exec is somehow scheduled") # Now remove it, so it can get created again File.unlink(fname) file[:content] = "some content" assert_events([:file_changed, :triggered], comp) assert(FileTest.exists?(fname), "File did not get recreated") # Now remove it, so it can get created again File.unlink(fname) # And tag our exec exec.tag("testrun") # And our file, so it runs file.tag("norun") Puppet[:tags] = "norun" file[:content] = "totally different content" assert(! file.insync?, "Uh, file is in sync?") assert_events([:file_changed, :triggered], comp) assert(FileTest.exists?(fname), "File did not get recreated") end def test_failed_reqs_mean_no_run exec = Puppet::Type.type(:exec).create( :command => "/bin/mkdir /this/path/cannot/possibly/exit", :title => "mkdir" ) file1 = Puppet::Type.type(:file).create( :title => "file1", :path => tempfile(), :require => exec, :ensure => :file ) file2 = Puppet::Type.type(:file).create( :title => "file2", :path => tempfile(), :require => file1, :ensure => :file ) comp = newcomp(exec, file1, file2) comp.finalize assert_apply(comp) assert(! FileTest.exists?(file1[:path]), "File got created even tho its dependency failed") assert(! FileTest.exists?(file2[:path]), "File got created even tho its deep dependency failed") end end def f(n) Puppet::Type.type(:file)["/tmp/#{n.to_s}"] end def test_relationship_graph one, two, middle, top = mktree {one => two, "f" => "c", "h" => middle}.each do |source, target| if source.is_a?(String) source = f(source) end if target.is_a?(String) target = f(target) end target[:require] = source end trans = Puppet::Transaction.new(top) graph = nil assert_nothing_raised do graph = trans.relationship_graph end assert_instance_of(Puppet::PGraph, graph, "Did not get relationship graph") # Make sure all of the components are gone comps = graph.vertices.find_all { |v| v.is_a?(Puppet::Type::Component)} assert(comps.empty?, "Deps graph still contains components") # It must be reversed because of how topsort works sorted = graph.topsort.reverse # Now make sure the appropriate edges are there and are in the right order - assert(graph.dependencies(f(:f)).include?(f(:c)), + assert(graph.dependents(f(:f)).include?(f(:c)), "c not marked a dep of f") assert(sorted.index(f(:c)) < sorted.index(f(:f)), "c is not before f") one.each do |o| two.each do |t| - assert(graph.dependencies(o).include?(t), + assert(graph.dependents(o).include?(t), "%s not marked a dep of %s" % [t.ref, o.ref]) assert(sorted.index(t) < sorted.index(o), "%s is not before %s" % [t.ref, o.ref]) end end trans.resources.leaves(middle).each do |child| - assert(graph.dependencies(f(:h)).include?(child), + assert(graph.dependents(f(:h)).include?(child), "%s not marked a dep of h" % [child.ref]) assert(sorted.index(child) < sorted.index(f(:h)), "%s is not before h" % child.ref) end # Lastly, make sure our 'g' vertex made it into the relationship # graph, since it's not involved in any relationships. assert(graph.vertex?(f(:g)), "Lost vertexes with no relations") # Now make the reversal graph and make sure all of the vertices made it into that reverse = graph.reversal %w{a b c d e f g h}.each do |letter| file = f(letter) assert(reverse.vertex?(file), "%s did not make it into reversal" % letter) end end # Test pre-evaluation generation def test_generate mkgenerator() do def generate ret = [] if title.length > 1 ret << self.class.create(:title => title[0..-2]) else return nil end ret end end yay = Puppet::Type.newgenerator :title => "yay" rah = Puppet::Type.newgenerator :title => "rah" comp = newcomp(yay, rah) trans = comp.evaluate assert_nothing_raised do trans.generate end %w{ya ra y r}.each do |name| assert(trans.resources.vertex?(Puppet::Type.type(:generator)[name]), "Generated %s was not a vertex" % name) end # Now make sure that cleanup gets rid of those generated types. assert_nothing_raised do trans.cleanup end %w{ya ra y r}.each do |name| assert(!trans.resources.vertex?(Puppet::Type.type(:generator)[name]), "Generated vertex %s was not removed from graph" % name) assert_nil(Puppet::Type.type(:generator)[name], "Generated vertex %s was not removed from class" % name) end end # Test mid-evaluation generation. def test_eval_generate $evaluated = [] type = mkgenerator() do def eval_generate ret = [] if title.length > 1 ret << self.class.create(:title => title[0..-2]) else return nil end ret end def evaluate $evaluated << self.title return [] end end yay = Puppet::Type.newgenerator :title => "yay" rah = Puppet::Type.newgenerator :title => "rah", :subscribe => yay comp = newcomp(yay, rah) trans = comp.evaluate trans.prepare # Now apply the resources, and make sure they appropriately generate # things. assert_nothing_raised("failed to apply yay") do trans.eval_resource(yay) end ya = type["ya"] assert(ya, "Did not generate ya") assert(trans.relgraph.vertex?(ya), "Did not add ya to rel_graph") # Now make sure the appropriate relationships were added assert(trans.relgraph.edge?(yay, ya), "parent was not required by child") assert(trans.relgraph.edge?(ya, rah), "rah was not subscribed to ya") # And make sure the relationship is a subscription with a callback, # not just a require. assert_equal({:callback => :refresh, :event => :ALL_EVENTS}, trans.relgraph[Puppet::Relationship.new(ya, rah)], "The label was not retained") # Now make sure it in turn eval_generates appropriately assert_nothing_raised("failed to apply yay") do trans.eval_resource(type["ya"]) end %w{y}.each do |name| res = type[name] assert(res, "Did not generate %s" % name) assert(trans.relgraph.vertex?(res), "Did not add %s to rel_graph" % name) end assert_nothing_raised("failed to eval_generate with nil response") do trans.eval_resource(type["y"]) end assert(trans.relgraph.edge?(yay, ya), "no edge was created for ya => yay") assert_nothing_raised("failed to apply rah") do trans.eval_resource(rah) end ra = type["ra"] assert(ra, "Did not generate ra") assert(trans.relgraph.vertex?(ra), "Did not add ra to rel_graph" % name) # Now make sure this generated resource has the same relationships as the generating # resource assert(trans.relgraph.edge?(yay, ra), "yay is not required by ra") assert(trans.relgraph.edge?(ya, ra), "ra is not subscribed to ya") # And make sure the relationship is a subscription with a callback, # not just a require. assert_equal({:callback => :refresh, :event => :ALL_EVENTS}, trans.relgraph[Puppet::Relationship.new(ya, ra)], "The label was not retained") # Now make sure that cleanup gets rid of those generated types. assert_nothing_raised do trans.cleanup end %w{ya ra y r}.each do |name| assert(!trans.relgraph.vertex?(type[name]), "Generated vertex %s was not removed from graph" % name) assert_nil(type[name], "Generated vertex %s was not removed from class" % name) end # Now, start over and make sure that everything gets evaluated. trans = comp.evaluate $evaluated.clear assert_nothing_raised do trans.evaluate end assert_equal(%w{yay ya y rah ra r}, $evaluated, "Not all resources were evaluated or not in the right order") end def test_tags res = Puppet::Type.newfile :path => tempfile() comp = newcomp(res) # Make sure they default to none assert_equal([], comp.evaluate.tags) # Make sure we get the main tags Puppet[:tags] = %w{this is some tags} assert_equal(%w{this is some tags}, comp.evaluate.tags) # And make sure they get processed correctly Puppet[:tags] = ["one", "two,three", "four"] assert_equal(%w{one two three four}, comp.evaluate.tags) # lastly, make sure we can override them trans = comp.evaluate trans.tags = ["one", "two,three", "four"] assert_equal(%w{one two three four}, comp.evaluate.tags) end def test_tagged? res = Puppet::Type.newfile :path => tempfile() comp = newcomp(res) trans = comp.evaluate assert(trans.tagged?(res), "tagged? defaulted to false") # Now set some tags trans.tags = %w{some tags} # And make sure it's false assert(! trans.tagged?(res), "matched invalid tags") # Set ignoretags and make sure it sticks trans.ignoretags = true assert(trans.tagged?(res), "tags were not ignored") # Now make sure we actually correctly match tags res[:tag] = "mytag" trans.ignoretags = false trans.tags = %w{notag} assert(! trans.tagged?(res), "tags incorrectly matched") trans.tags = %w{mytag yaytag} assert(trans.tagged?(res), "tags should have matched") end # We don't want to purge resources that have relationships with other resources, # so we want our transactions to check for that. def test_required_resources_not_deleted @file = Puppet::Type.type(:file) path1 = tempfile() path2 = tempfile() # Create our first file File.open(path1, "w") { |f| f.puts "yay" } # Create a couple of related resources file1 = @file.create :title => "dependee", :path => path1, :ensure => :absent file2 = @file.create :title => "depender", :path => path2, :content => "some stuff", :require => file1 # Now make sure we don't actually delete the first file assert_apply(file1, file2) assert(FileTest.exists?(path1), "required file was deleted") end end # $Id$ diff --git a/test/types/file.rb b/test/types/file.rb index 917b2e755..f93d3670b 100755 --- a/test/types/file.rb +++ b/test/types/file.rb @@ -1,1774 +1,1767 @@ #!/usr/bin/env ruby $:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ require 'puppet' require 'fileutils' require 'puppettest' class TestFile < Test::Unit::TestCase include PuppetTest::FileTesting # hmmm # this is complicated, because we store references to the created # objects in a central store def mkfile(hash) file = nil assert_nothing_raised { file = Puppet.type(:file).create(hash) } return file end def mktestfile # because luke's home directory is on nfs, it can't be used for testing # as root tmpfile = tempfile() File.open(tmpfile, "w") { |f| f.puts rand(100) } @@tmpfiles.push tmpfile mkfile(:name => tmpfile) end def setup super @file = Puppet::Type.type(:file) begin initstorage rescue system("rm -rf %s" % Puppet[:statefile]) end end def teardown Puppet::Storage.clear system("rm -rf %s" % Puppet[:statefile]) super end def initstorage Puppet::Storage.init Puppet::Storage.load end def clearstorage Puppet::Storage.store Puppet::Storage.clear end def test_owner file = mktestfile() users = {} count = 0 # collect five users Etc.passwd { |passwd| if count > 5 break else count += 1 end users[passwd.uid] = passwd.name } fake = {} # find a fake user while true a = rand(1000) begin Etc.getpwuid(a) rescue fake[a] = "fakeuser" break end end uid, name = users.shift us = {} us[uid] = name users.each { |uid, name| assert_apply(file) assert_nothing_raised() { file[:owner] = name } assert_nothing_raised() { file.retrieve } assert_apply(file) } end def test_group file = mktestfile() [%x{groups}.chomp.split(/ /), Process.groups].flatten.each { |group| assert_nothing_raised() { file[:group] = group } assert(file.state(:group)) assert(file.state(:group).should) } end if Puppet::SUIDManager.uid == 0 def test_createasuser dir = tmpdir() user = nonrootuser() path = File.join(tmpdir, "createusertesting") @@tmpfiles << path file = nil assert_nothing_raised { file = Puppet.type(:file).create( :path => path, :owner => user.name, :ensure => "file", :mode => "755" ) } comp = newcomp("createusertest", file) assert_events([:file_created], comp) end def test_nofollowlinks basedir = tempfile() Dir.mkdir(basedir) file = File.join(basedir, "file") link = File.join(basedir, "link") File.open(file, "w", 0644) { |f| f.puts "yayness"; f.flush } File.symlink(file, link) # First test 'user' user = nonrootuser() inituser = File.lstat(link).uid File.lchown(inituser, nil, link) obj = nil assert_nothing_raised { obj = Puppet.type(:file).create( :title => link, :owner => user.name ) } obj.retrieve # Make sure it defaults to managing the link assert_events([:file_changed], obj) assert_equal(user.uid, File.lstat(link).uid) assert_equal(inituser, File.stat(file).uid) File.chown(inituser, nil, file) File.lchown(inituser, nil, link) # Try following obj[:links] = :follow assert_events([:file_changed], obj) assert_equal(user.uid, File.stat(file).uid) assert_equal(inituser, File.lstat(link).uid) # And then explicitly managing File.chown(inituser, nil, file) File.lchown(inituser, nil, link) obj[:links] = :manage assert_events([:file_changed], obj) assert_equal(user.uid, File.lstat(link).uid) assert_equal(inituser, File.stat(file).uid) obj.delete(:owner) obj[:links] = :ignore # And then test 'group' group = nonrootgroup initgroup = File.stat(file).gid obj[:group] = group.name assert_events([:file_changed], obj) assert_equal(initgroup, File.stat(file).gid) assert_equal(group.gid, File.lstat(link).gid) File.chown(nil, initgroup, file) File.lchown(nil, initgroup, link) obj[:links] = :follow assert_events([:file_changed], obj) assert_equal(group.gid, File.stat(file).gid) File.chown(nil, initgroup, file) File.lchown(nil, initgroup, link) obj[:links] = :manage assert_events([:file_changed], obj) assert_equal(group.gid, File.lstat(link).gid) assert_equal(initgroup, File.stat(file).gid) end def test_ownerasroot file = mktestfile() users = {} count = 0 # collect five users Etc.passwd { |passwd| if count > 5 break else count += 1 end next if passwd.uid < 0 users[passwd.uid] = passwd.name } fake = {} # find a fake user while true a = rand(1000) begin Etc.getpwuid(a) rescue fake[a] = "fakeuser" break end end users.each { |uid, name| assert_nothing_raised() { file[:owner] = name } changes = [] assert_nothing_raised() { changes << file.evaluate } assert(changes.length > 0) assert_apply(file) file.retrieve assert(file.insync?()) assert_nothing_raised() { file[:owner] = uid } assert_apply(file) file.retrieve # make sure changing to number doesn't cause a sync assert(file.insync?()) } # We no longer raise an error here, because we check at run time #fake.each { |uid, name| # assert_raise(Puppet::Error) { # file[:owner] = name # } # assert_raise(Puppet::Error) { # file[:owner] = uid # } #} end def test_groupasroot file = mktestfile() [%x{groups}.chomp.split(/ /), Process.groups].flatten.each { |group| assert_nothing_raised() { file[:group] = group } assert(file.state(:group)) assert(file.state(:group).should) assert_apply(file) file.retrieve assert(file.insync?()) assert_nothing_raised() { file.delete(:group) } } end if Facter.value(:operatingsystem) == "Darwin" def test_sillyowner file = tempfile() File.open(file, "w") { |f| f.puts "" } File.chown(-2, nil, file) assert(File.stat(file).uid > 120000, "eh?") user = nonrootuser obj = Puppet::Type.newfile( :path => file, :owner => user.name ) assert_apply(obj) assert_equal(user.uid, File.stat(file).uid) end end else $stderr.puts "Run as root for complete owner and group testing" end def test_create %w{a b c d}.collect { |name| tempfile() + name.to_s }.each { |path| file =nil assert_nothing_raised() { file = Puppet.type(:file).create( :name => path, :ensure => "file" ) } assert_events([:file_created], file) assert_events([], file) assert(FileTest.file?(path), "File does not exist") assert(file.insync?()) @@tmpfiles.push path } end def test_create_dir basedir = tempfile() Dir.mkdir(basedir) %w{a b c d}.collect { |name| "#{basedir}/%s" % name }.each { |path| file = nil assert_nothing_raised() { file = Puppet.type(:file).create( :name => path, :ensure => "directory" ) } assert(! FileTest.directory?(path), "Directory %s already exists" % [path]) assert_events([:directory_created], file) assert_events([], file) assert(file.insync?()) assert(FileTest.directory?(path)) @@tmpfiles.push path } end def test_modes file = mktestfile # Set it to something else initially File.chmod(0775, file.title) [0644,0755,0777,0641].each { |mode| assert_nothing_raised() { file[:mode] = mode } assert_events([:file_changed], file) assert_events([], file) assert(file.insync?()) assert_nothing_raised() { file.delete(:mode) } } end def test_checksums types = %w{md5 md5lite timestamp time} exists = "/tmp/sumtest-exists" nonexists = "/tmp/sumtest-nonexists" @@tmpfiles << exists @@tmpfiles << nonexists # try it both with files that exist and ones that don't files = [exists, nonexists] initstorage File.open(exists,File::CREAT|File::TRUNC|File::WRONLY) { |of| of.puts "initial text" } types.each { |type| files.each { |path| if Puppet[:debug] Puppet.warning "Testing %s on %s" % [type,path] end file = nil events = nil # okay, we now know that we have a file... assert_nothing_raised() { file = Puppet.type(:file).create( :name => path, :ensure => "file", :checksum => type ) } trans = nil file.retrieve if file.title !~ /nonexists/ sum = file.state(:checksum) assert(sum.insync?, "file is not in sync") end events = assert_apply(file) assert(! events.include?(:file_changed), "File incorrectly changed") assert_events([], file) # We have to sleep because the time resolution of the time-based # mechanisms is greater than one second sleep 1 if type =~ /time/ assert_nothing_raised() { File.open(path,File::CREAT|File::TRUNC|File::WRONLY) { |of| of.puts "some more text, yo" } } Puppet.type(:file).clear # now recreate the file assert_nothing_raised() { file = Puppet.type(:file).create( :name => path, :checksum => type ) } trans = nil assert_events([:file_changed], file) # Run it a few times to make sure we aren't getting # spurious changes. assert_nothing_raised do file.state(:checksum).retrieve end assert(file.state(:checksum).insync?, "checksum is not in sync") sleep 1.1 if type =~ /time/ assert_nothing_raised() { File.unlink(path) File.open(path,File::CREAT|File::TRUNC|File::WRONLY) { |of| # We have to put a certain amount of text in here or # the md5-lite test fails 2.times { of.puts rand(100) } of.flush } } assert_events([:file_changed], file) # verify that we're actually getting notified when a file changes assert_nothing_raised() { Puppet.type(:file).clear } if path =~ /nonexists/ File.unlink(path) end } } end def cyclefile(path) # i had problems with using :name instead of :path [:name,:path].each { |param| file = nil changes = nil comp = nil trans = nil initstorage assert_nothing_raised { file = Puppet.type(:file).create( param => path, :recurse => true, :checksum => "md5" ) } comp = Puppet.type(:component).create( :name => "component" ) comp.push file assert_nothing_raised { trans = comp.evaluate } assert_nothing_raised { trans.evaluate } clearstorage Puppet::Type.allclear } end def test_localrecurse # Create a test directory path = tempfile() dir = @file.create :path => path, :mode => 0755, :recurse => true Dir.mkdir(path) # Make sure we return nothing when there are no children ret = nil assert_nothing_raised() { ret = dir.localrecurse(true) } assert_equal([], ret, "empty dir returned children") # Now make a file and make sure we get it test = File.join(path, "file") File.open(test, "w") { |f| f.puts "yay" } assert_nothing_raised() { ret = dir.localrecurse(true) } fileobj = @file[test] assert(fileobj, "child object was not created") assert_equal([fileobj], ret, "child object was not returned") # check that the file lists us as a dependency assert_equal([[:file, dir.title]], fileobj[:require], "dependency was not set up") # And that it inherited our recurse setting assert_equal(true, fileobj[:recurse], "file did not inherit recurse") # Make sure it's not returned again assert_nothing_raised() { ret = dir.localrecurse(true) } assert_equal([], ret, "child object was returned twice") # Now just for completion, make sure we will return many files files = [] 10.times do |i| f = File.join(path, i.to_s) files << f File.open(f, "w") do |o| o.puts "" end end assert_nothing_raised() { ret = dir.localrecurse(true) } assert_equal(files.sort, ret.collect { |f| f.title }, "child object was returned twice") # Clean everything up and start over files << test files.each do |f| File.unlink(f) end # Now make sure we correctly ignore things dir[:ignore] = "*.out" bad = File.join(path, "test.out") good = File.join(path, "yayness") [good, bad].each do |f| File.open(f, "w") { |o| o.puts "" } end assert_nothing_raised() { ret = dir.localrecurse(true) } assert_equal([good], ret.collect { |f| f.title }, "ignore failed") # Now make sure purging works dir[:purge] = true dir[:ignore] = "svn" assert_nothing_raised() { ret = dir.localrecurse(true) } assert_equal([bad], ret.collect { |f| f.title }, "purge failed") badobj = @file[bad] assert(badobj, "did not create bad object") assert_equal(:absent, badobj.should(:ensure), "ensure was not set to absent on bad object") end def test_recurse basedir = tempfile() FileUtils.mkdir_p(basedir) # Create our file dir = nil assert_nothing_raised { dir = Puppet.type(:file).create( :path => basedir, :check => %w{owner mode group} ) } return_nil = false # and monkey-patch it [:localrecurse, :sourcerecurse, :linkrecurse].each do |m| dir.meta_def(m) do |recurse| if return_nil # for testing nil return, of course return nil else return [recurse] end end end # First try it with recurse set to false dir[:recurse] = false assert_nothing_raised do assert_nil(dir.recurse) end # Now try it with the different valid positive values [true, "true", "inf", 50].each do |value| assert_nothing_raised { dir[:recurse] = value} # Now make sure the methods are called appropriately ret = nil assert_nothing_raised do ret = dir.recurse end # We should only call the localrecurse method, so make sure # that's the case if value == 50 # Make sure our counter got decremented assert_equal([49], ret, "did not call localrecurse") else assert_equal([true], ret, "did not call localrecurse") end end # Make sure it doesn't recurse when we've set recurse to false [false, "false"].each do |value| assert_nothing_raised { dir[:recurse] = value } ret = nil assert_nothing_raised() { ret = dir.recurse } assert_nil(ret) end dir[:recurse] = true # Now add a target, so we do the linking thing dir[:target] = tempfile() ret = nil assert_nothing_raised { ret = dir.recurse } assert_equal([true, true], ret, "did not call linkrecurse") # And add a source, and make sure we call that dir[:source] = tempfile() assert_nothing_raised { ret = dir.recurse } assert_equal([true, true, true], ret, "did not call linkrecurse") # Lastly, make sure we correctly handle returning nil return_nil = true assert_nothing_raised { ret = dir.recurse } end def test_recurse? file = Puppet::Type.type(:file).create :path => tempfile # Make sure we default to false assert(! file.recurse?, "Recurse defaulted to true") [true, "true", 10, "inf"].each do |value| file[:recurse] = value assert(file.recurse?, "%s did not cause recursion" % value) end [false, "false", 0].each do |value| file[:recurse] = value assert(! file.recurse?, "%s caused recursion" % value) end end def test_recursion basedir = tempfile() subdir = File.join(basedir, "subdir") tmpfile = File.join(basedir,"testing") FileUtils.mkdir_p(subdir) dir = nil [true, "true", "inf", 50].each do |value| assert_nothing_raised { dir = Puppet.type(:file).create( :path => basedir, :recurse => value, :check => %w{owner mode group} ) } children = nil assert_nothing_raised { children = dir.eval_generate } assert_equal([subdir], children.collect {|c| c.title }, "Incorrect generated children") dir.class[subdir].remove File.open(tmpfile, "w") { |f| f.puts "yayness" } assert_nothing_raised { children = dir.eval_generate } assert_equal([subdir, tmpfile].sort, children.collect {|c| c.title }.sort, "Incorrect generated children") File.unlink(tmpfile) #system("rm -rf %s" % basedir) Puppet.type(:file).clear end end def test_filetype_retrieval file = nil # Verify it retrieves files of type directory assert_nothing_raised { file = Puppet.type(:file).create( :name => tmpdir(), :check => :type ) } assert_nothing_raised { file.evaluate } assert_equal("directory", file.state(:type).is) # And then check files assert_nothing_raised { file = Puppet.type(:file).create( :name => tempfile(), :ensure => "file" ) } assert_apply(file) file[:check] = "type" assert_apply(file) assert_equal("file", file.state(:type).is) file[:type] = "directory" assert_nothing_raised { file.retrieve } # The 'retrieve' method sets @should to @is, so they're never # out of sync. It's a read-only class. assert(file.insync?) end def test_remove basedir = tempfile() subdir = File.join(basedir, "this") FileUtils.mkdir_p(subdir) dir = nil assert_nothing_raised { dir = Puppet.type(:file).create( :path => basedir, :recurse => true, :check => %w{owner mode group} ) } assert_nothing_raised { dir.eval_generate } obj = nil assert_nothing_raised { obj = Puppet.type(:file)[subdir] } assert(obj, "Could not retrieve subdir object") assert_nothing_raised { obj.remove(true) } assert_nothing_raised { obj = Puppet.type(:file)[subdir] } assert_nil(obj, "Retrieved removed object") end def test_path dir = tempfile() path = File.join(dir, "subdir") assert_nothing_raised("Could not make file") { FileUtils.mkdir_p(File.dirname(path)) File.open(path, "w") { |f| f.puts "yayness" } } file = nil dirobj = nil assert_nothing_raised("Could not make file object") { dirobj = Puppet.type(:file).create( :path => dir, :recurse => true, :check => %w{mode owner group} ) } assert_nothing_raised { dirobj.eval_generate } assert_nothing_raised { file = dirobj.class[path] } assert(file, "Could not retrieve file object") assert_equal("file=%s" % file.title, file.path) end def test_autorequire basedir = tempfile() subfile = File.join(basedir, "subfile") baseobj = Puppet.type(:file).create( :name => basedir, :ensure => "directory" ) subobj = Puppet.type(:file).create( :name => subfile, :ensure => "file" ) edge = nil assert_nothing_raised do edge = subobj.autorequire.shift end assert_equal(baseobj, edge.source, "file did not require its parent dir") assert_equal(subobj, edge.target, "file did not require its parent dir") end def test_content file = tempfile() str = "This is some content" obj = nil assert_nothing_raised { obj = Puppet.type(:file).create( :name => file, :content => str ) } assert(!obj.insync?, "Object is incorrectly in sync") assert_events([:file_created], obj) obj.retrieve assert(obj.insync?, "Object is not in sync") text = File.read(file) assert_equal(str, text, "Content did not copy correctly") newstr = "Another string, yo" obj[:content] = newstr assert(!obj.insync?, "Object is incorrectly in sync") assert_events([:file_changed], obj) text = File.read(file) assert_equal(newstr, text, "Content did not copy correctly") obj.retrieve assert(obj.insync?, "Object is not in sync") end # Unfortunately, I know this fails def disabled_test_recursivemkdir path = tempfile() subpath = File.join(path, "this", "is", "a", "dir") file = nil assert_nothing_raised { file = Puppet.type(:file).create( :name => subpath, :ensure => "directory", :recurse => true ) } comp = newcomp("yay", file) comp.finalize assert_apply(comp) #assert_events([:directory_created], comp) assert(FileTest.directory?(subpath), "Did not create directory") end # Make sure that content updates the checksum on the same run def test_checksumchange_for_content dest = tempfile() File.open(dest, "w") { |f| f.puts "yayness" } file = nil assert_nothing_raised { file = Puppet.type(:file).create( :name => dest, :checksum => "md5", :content => "This is some content" ) } file.retrieve assert_events([:file_changed], file) file.retrieve assert_events([], file) end # Make sure that content updates the checksum on the same run def test_checksumchange_for_ensure dest = tempfile() file = nil assert_nothing_raised { file = Puppet.type(:file).create( :name => dest, :checksum => "md5", :ensure => "file" ) } file.retrieve assert_events([:file_created], file) file.retrieve assert_events([], file) end # Make sure that content gets used before ensure def test_contentbeatsensure dest = tempfile() file = nil assert_nothing_raised { file = Puppet.type(:file).create( :name => dest, :ensure => "file", :content => "this is some content, yo" ) } file.retrieve assert_events([:file_created], file) file.retrieve assert_events([], file) assert_events([], file) end def test_nameandpath path = tempfile() file = nil assert_nothing_raised { file = Puppet.type(:file).create( :title => "fileness", :path => path, :content => "this is some content" ) } assert_apply(file) assert(FileTest.exists?(path)) end # Make sure that a missing group isn't fatal at object instantiation time. def test_missinggroup file = nil assert_nothing_raised { file = Puppet.type(:file).create( :path => tempfile(), :group => "fakegroup" ) } assert(file.state(:group), "Group state failed") end def test_modecreation path = tempfile() file = Puppet.type(:file).create( :path => path, :ensure => "file", :mode => "0777" ) assert_apply(file) assert_equal(0777, File.stat(path).mode & 007777) File.unlink(path) file[:ensure] = "directory" assert_apply(file) assert_equal(0777, File.stat(path).mode & 007777) end def test_followlinks basedir = tempfile() Dir.mkdir(basedir) file = File.join(basedir, "file") link = File.join(basedir, "link") File.open(file, "w", 0644) { |f| f.puts "yayness"; f.flush } File.symlink(file, link) obj = nil assert_nothing_raised { obj = Puppet.type(:file).create( :path => link, :mode => "755" ) } obj.retrieve assert_events([], obj) # Assert that we default to not following links assert_equal("%o" % 0644, "%o" % (File.stat(file).mode & 007777)) # Assert that we can manage the link directly, but modes still don't change obj[:links] = :manage assert_events([], obj) assert_equal("%o" % 0644, "%o" % (File.stat(file).mode & 007777)) obj[:links] = :follow assert_events([:file_changed], obj) assert_equal("%o" % 0755, "%o" % (File.stat(file).mode & 007777)) # Now verify that content and checksum don't update, either obj.delete(:mode) obj[:checksum] = "md5" obj[:links] = :ignore assert_events([], obj) File.open(file, "w") { |f| f.puts "more text" } assert_events([], obj) obj[:links] = :follow assert_events([], obj) File.open(file, "w") { |f| f.puts "even more text" } assert_events([:file_changed], obj) obj.delete(:checksum) obj[:content] = "this is some content" obj[:links] = :ignore assert_events([], obj) File.open(file, "w") { |f| f.puts "more text" } assert_events([], obj) obj[:links] = :follow assert_events([:file_changed], obj) end # If both 'ensure' and 'content' are used, make sure that all of the other # states are handled correctly. def test_contentwithmode path = tempfile() file = nil assert_nothing_raised { file = Puppet.type(:file).create( :path => path, :ensure => "file", :content => "some text\n", :mode => 0755 ) } assert_apply(file) assert_equal("%o" % 0755, "%o" % (File.stat(path).mode & 007777)) end # Make sure we can create symlinks def test_symlinks path = tempfile() link = tempfile() File.open(path, "w") { |f| f.puts "yay" } file = nil assert_nothing_raised { file = Puppet.type(:file).create( :title => "somethingelse", :ensure => path, :path => link ) } assert_events([:link_created], file) assert(FileTest.symlink?(link), "Link was not created") assert_equal(path, File.readlink(link), "Link was created incorrectly") # Make sure running it again works assert_events([], file) assert_events([], file) assert_events([], file) end def test_linkrecurse dest = tempfile() link = @file.create :path => tempfile(), :recurse => true, :ensure => dest ret = nil # Start with nothing, just to make sure we get nothing back assert_nothing_raised { ret = link.linkrecurse(true) } assert_nil(ret, "got a return when the dest doesn't exist") # then with a directory with only one file Dir.mkdir(dest) one = File.join(dest, "one") File.open(one, "w") { |f| f.puts "" } link[:ensure] = dest assert_nothing_raised { ret = link.linkrecurse(true) } assert_equal(:directory, link.should(:ensure), "ensure was not set to directory") assert_equal([File.join(link.title, "one")], ret.collect { |f| f.title }, "Did not get linked file") oneobj = @file[File.join(link.title, "one")] assert_equal(one, oneobj.should(:target), "target was not set correctly") oneobj.remove File.unlink(one) # Then make sure we get multiple files returns = [] 5.times do |i| path = File.join(dest, i.to_s) returns << File.join(link.title, i.to_s) File.open(path, "w") { |f| f.puts "" } end assert_nothing_raised { ret = link.linkrecurse(true) } assert_equal(returns.sort, ret.collect { |f| f.title }, "Did not get links back") returns.each do |path| obj = @file[path] assert(path, "did not get obj for %s" % path) sdest = File.join(dest, File.basename(path)) assert_equal(sdest, obj.should(:target), "target was not set correctly for %s" % path) end end def test_simplerecursivelinking source = tempfile() path = tempfile() subdir = File.join(source, "subdir") file = File.join(subdir, "file") system("mkdir -p %s" % subdir) system("touch %s" % file) link = nil assert_nothing_raised { link = Puppet.type(:file).create( :ensure => source, :path => path, :recurse => true ) } assert_apply(link) sublink = File.join(path, "subdir") linkpath = File.join(sublink, "file") assert(File.directory?(path), "dest is not a dir") assert(File.directory?(sublink), "subdest is not a dir") assert(File.symlink?(linkpath), "path is not a link") assert_equal(file, File.readlink(linkpath)) assert_nil(@file[sublink], "objects were not removed") assert_events([], link) end def test_recursivelinking source = tempfile() dest = tempfile() files = [] dirs = [] # Make a bunch of files and dirs Dir.mkdir(source) Dir.chdir(source) do system("mkdir -p %s" % "some/path/of/dirs") system("mkdir -p %s" % "other/path/of/dirs") system("touch %s" % "file") system("touch %s" % "other/file") system("touch %s" % "some/path/of/file") system("touch %s" % "some/path/of/dirs/file") system("touch %s" % "other/path/of/file") files = %x{find . -type f}.chomp.split(/\n/) dirs = %x{find . -type d}.chomp.split(/\n/).reject{|d| d =~ /^\.+$/ } end link = nil assert_nothing_raised { link = Puppet.type(:file).create( :ensure => source, :path => dest, :recurse => true ) } assert_apply(link) files.each do |f| f.sub!(/^\.#{File::SEPARATOR}/, '') path = File.join(dest, f) assert(FileTest.exists?(path), "Link %s was not created" % path) assert(FileTest.symlink?(path), "%s is not a link" % f) target = File.readlink(path) assert_equal(File.join(source, f), target) end dirs.each do |d| d.sub!(/^\.#{File::SEPARATOR}/, '') path = File.join(dest, d) assert(FileTest.exists?(path), "Dir %s was not created" % path) assert(FileTest.directory?(path), "%s is not a directory" % d) end end def test_localrelativelinks dir = tempfile() Dir.mkdir(dir) source = File.join(dir, "source") File.open(source, "w") { |f| f.puts "yay" } dest = File.join(dir, "link") link = nil assert_nothing_raised { link = Puppet.type(:file).create( :path => dest, :ensure => "source" ) } assert_events([:link_created], link) assert(FileTest.symlink?(dest), "Did not create link") assert_equal("source", File.readlink(dest)) assert_equal("yay\n", File.read(dest)) end def test_recursivelinkingmissingtarget source = tempfile() dest = tempfile() objects = [] objects << Puppet.type(:exec).create( :command => "mkdir %s; touch %s/file" % [source, source], :title => "yay", :path => ENV["PATH"] ) objects << Puppet.type(:file).create( :ensure => source, :path => dest, :recurse => true, :require => objects[0] ) assert_apply(*objects) link = File.join(dest, "file") assert(FileTest.symlink?(link), "Did not make link") assert_equal(File.join(source, "file"), File.readlink(link)) end def test_backupmodes file = tempfile() newfile = tempfile() File.open(file, "w", 0411) { |f| f.puts "yayness" } obj = nil assert_nothing_raised { obj = Puppet::Type.type(:file).create( :path => file, :content => "rahness\n" ) } # user = group = nil # if Process.uid == 0 # user = nonrootuser # group = nonrootgroup # obj[:owner] = user.name # obj[:group] = group.name # File.chown(user.uid, group.gid, file) # end assert_apply(obj) backupfile = file + obj[:backup] @@tmpfiles << backupfile assert(FileTest.exists?(backupfile), "Backup file %s does not exist" % backupfile) assert_equal(0411, filemode(backupfile), "File mode is wrong for backupfile") # if Process.uid == 0 # assert_equal(user.uid, File.stat(backupfile).uid) # assert_equal(group.gid, File.stat(backupfile).gid) # end bucket = "bucket" bpath = tempfile() Dir.mkdir(bpath) Puppet::Type.type(:filebucket).create( :title => bucket, :path => bpath ) obj[:backup] = bucket obj[:content] = "New content" assert_apply(obj) bucketedpath = File.join(bpath, "18cc17fa3047fcc691fdf49c0a7f539a", "contents") assert_equal(0440, filemode(bucketedpath)) end def test_largefilechanges source = tempfile() dest = tempfile() # Now make a large file File.open(source, "w") { |f| 500.times { |i| f.puts "line %s" % i } } obj = Puppet::Type.type(:file).create( :title => dest, :source => source ) assert_events([:file_created], obj) File.open(source, File::APPEND|File::WRONLY) { |f| f.puts "another line" } assert_events([:file_changed], obj) # Now modify the dest file File.open(dest, File::APPEND|File::WRONLY) { |f| f.puts "one more line" } assert_events([:file_changed, :file_changed], obj) end def test_replacefilewithlink path = tempfile() link = tempfile() File.open(path, "w") { |f| f.puts "yay" } File.open(link, "w") { |f| f.puts "a file" } file = nil assert_nothing_raised { file = Puppet.type(:file).create( :ensure => path, :path => link ) } assert_events([:link_created], file) assert(FileTest.symlink?(link), "Link was not created") assert_equal(path, File.readlink(link), "Link was created incorrectly") end def test_replacedirwithlink path = tempfile() link = tempfile() File.open(path, "w") { |f| f.puts "yay" } Dir.mkdir(link) File.open(File.join(link, "yay"), "w") do |f| f.puts "boo" end file = nil assert_nothing_raised { file = Puppet.type(:file).create( :ensure => path, :path => link, :backup => false ) } # First run through without :force assert_events([], file) assert(FileTest.directory?(link), "Link replaced dir without force") assert_nothing_raised { file[:force] = true } assert_events([:link_created], file) assert(FileTest.symlink?(link), "Link was not created") assert_equal(path, File.readlink(link), "Link was created incorrectly") end def test_replace_links_with_files base = tempfile() Dir.mkdir(base) file = File.join(base, "file") link = File.join(base, "link") File.open(file, "w") { |f| f.puts "yayness" } File.symlink(file, link) obj = Puppet::Type.type(:file).create( :path => link, :ensure => "file" ) assert_apply(obj) assert_equal("yayness\n", File.read(file), "Original file got changed") assert_equal("file", File.lstat(link).ftype, "File is still a link") end def test_no_erase_linkedto_files base = tempfile() Dir.mkdir(base) dirs = {} %w{other source target}.each do |d| dirs[d] = File.join(base, d) Dir.mkdir(dirs[d]) end file = File.join(dirs["other"], "file") sourcefile = File.join(dirs["source"], "sourcefile") link = File.join(dirs["target"], "link") File.open(file, "w") { |f| f.puts "other" } File.open(sourcefile, "w") { |f| f.puts "source" } File.symlink(file, link) obj = Puppet::Type.type(:file).create( :path => dirs["target"], :ensure => "file", :source => dirs["source"], :recurse => true ) trans = assert_events([:file_created, :file_created], obj) newfile = File.join(dirs["target"], "sourcefile") assert(File.exists?(newfile), "File did not get copied") assert_equal(File.read(sourcefile), File.read(newfile), "File did not get copied correctly.") assert_equal("other\n", File.read(file), "Original file got changed") assert_equal("file", File.lstat(link).ftype, "File is still a link") end def test_replace_links dest = tempfile() otherdest = tempfile() link = tempfile() File.open(dest, "w") { |f| f.puts "boo" } File.open(otherdest, "w") { |f| f.puts "yay" } obj = Puppet::Type.type(:file).create( :path => link, :ensure => otherdest ) assert_apply(obj) assert_equal(otherdest, File.readlink(link), "Link did not get created") obj[:ensure] = dest assert_apply(obj) assert_equal(dest, File.readlink(link), "Link did not get changed") end def test_file_with_spaces dir = tempfile() Dir.mkdir(dir) source = File.join(dir, "file spaces") dest = File.join(dir, "another space") File.open(source, "w") { |f| f.puts :yay } obj = Puppet::Type.type(:file).create( :path => dest, :source => source ) assert(obj, "Did not create file") assert_apply(obj) assert(FileTest.exists?(dest), "File did not get created") end def test_present_matches_anything path = tempfile() file = Puppet::Type.newfile(:path => path, :ensure => :present) file.retrieve assert(! file.insync?, "File incorrectly in sync") # Now make a file File.open(path, "w") { |f| f.puts "yay" } file.retrieve assert(file.insync?, "File not in sync") # Now make a directory File.unlink(path) Dir.mkdir(path) file.retrieve assert(file.insync?, "Directory not considered 'present'") Dir.rmdir(path) # Now make a link file[:links] = :manage otherfile = tempfile() File.symlink(otherfile, path) file.retrieve assert(file.insync?, "Symlink not considered 'present'") File.unlink(path) # Now set some content, and make sure it works file[:content] = "yayness" assert_apply(file) assert_equal("yayness", File.read(path), "Content did not get set correctly") end # Make sure unmanaged files are be purged. def test_purge sourcedir = tempfile() destdir = tempfile() Dir.mkdir(sourcedir) Dir.mkdir(destdir) sourcefile = File.join(sourcedir, "sourcefile") dsourcefile = File.join(destdir, "sourcefile") localfile = File.join(destdir, "localfile") purgee = File.join(destdir, "to_be_purged") File.open(sourcefile, "w") { |f| f.puts "funtest" } # this file should get removed File.open(purgee, "w") { |f| f.puts "footest" } lfobj = Puppet::Type.newfile(:title => "localfile", :path => localfile, :content => "rahtest") destobj = Puppet::Type.newfile(:title => "destdir", :path => destdir, :source => sourcedir, :recurse => true) - puts "a" comp = newcomp(lfobj, destobj) - # trans = comp.evaluate assert_apply(comp) - # assert_nothing_raised { trans.evaluate } - puts "b" - # graph = trans.relgraph - # graph.to_jpg("/Users/luke/Desktop/pics", "purging") assert(FileTest.exists?(dsourcefile), "File did not get copied") assert(FileTest.exists?(localfile), "File did not get created") assert(FileTest.exists?(purgee), "File got prematurely purged") assert_nothing_raised { destobj[:purge] = true } assert_apply(comp) - system("find %s" % destdir) assert(FileTest.exists?(dsourcefile), "File got purged") assert(FileTest.exists?(localfile), "File got purged") assert(! FileTest.exists?(purgee), "File did not get purged") end # Testing #274. Make sure target can be used without 'ensure'. def test_target_without_ensure source = tempfile() dest = tempfile() File.open(source, "w") { |f| f.puts "funtest" } obj = nil assert_nothing_raised { obj = Puppet::Type.newfile(:path => dest, :target => source) } assert_apply(obj) end def test_autorequire_owner_and_group file = tempfile() comp = nil user = nil group =nil home = nil ogroup = nil assert_nothing_raised { user = Puppet.type(:user).create( :name => "pptestu", :home => file, :gid => "pptestg" ) home = Puppet.type(:file).create( :path => file, :owner => "pptestu", :group => "pptestg", :ensure => "directory" ) group = Puppet.type(:group).create( :name => "pptestg" ) comp = newcomp(user, group, home) } # Now make sure we get a relationship for each of these rels = nil assert_nothing_raised { rels = home.autorequire } assert(rels.detect { |e| e.source == user }, "owner was not autorequired") assert(rels.detect { |e| e.source == group }, "group was not autorequired") end # Testing #309 -- //my/file => /my/file def test_slash_deduplication ["/my/////file/for//testing", "//my/file/for/testing///", "/my/file/for/testing"].each do |path| file = nil assert_nothing_raised do file = Puppet::Type.newfile(:path => path) end assert_equal("/my/file/for/testing", file.title) assert_equal(file, Puppet::Type.type(:file)["/my/file/for/testing"]) Puppet::Type.type(:file).clear end end # Testing #304 def test_links_to_directories link = tempfile() file = tempfile() dir = tempfile() Dir.mkdir(dir) bucket = Puppet::Type.newfilebucket :name => "main" File.symlink(dir, link) File.open(file, "w") { |f| f.puts "" } assert_equal(dir, File.readlink(link)) obj = Puppet::Type.newfile :path => link, :ensure => :link, :target => file, :recurse => false, :backup => "main" assert_apply(obj) assert_equal(file, File.readlink(link)) end # Testing #303 def test_nobackups_with_links link = tempfile() new = tempfile() File.open(link, "w") { |f| f.puts "old" } File.open(new, "w") { |f| f.puts "new" } obj = Puppet::Type.newfile :path => link, :ensure => :link, :target => new, :recurse => true, :backup => false assert_nothing_raised do obj.handlebackup end bfile = [link, "puppet-bak"].join(".") assert(! FileTest.exists?(bfile), "Backed up when told not to") assert_apply(obj) assert(! FileTest.exists?(bfile), "Backed up when told not to") end # Make sure we consistently handle backups for all cases. def test_ensure_with_backups # We've got three file types, so make sure we can replace any type # with the other type and that backups are done correctly. types = [:file, :directory, :link] dir = tempfile() path = File.join(dir, "test") linkdest = tempfile() creators = { :file => proc { File.open(path, "w") { |f| f.puts "initial" } }, :directory => proc { Dir.mkdir(path) }, :link => proc { File.symlink(linkdest, path) } } bucket = Puppet::Type.newfilebucket :name => "main", :path => tempfile() obj = Puppet::Type.newfile :path => path, :force => true, :links => :manage Puppet[:trace] = true ["main", false].each do |backup| obj[:backup] = backup obj.finish types.each do |should| types.each do |is| # It makes no sense to replace a directory with a directory # next if should == :directory and is == :directory Dir.mkdir(dir) # Make the thing creators[is].call obj[:ensure] = should if should == :link obj[:target] = linkdest else if obj.state(:target) obj.delete(:target) end end # First try just removing the initial data assert_nothing_raised do obj.remove_existing(should) end unless is == should # Make sure the original is gone assert(! FileTest.exists?(obj[:path]), "remove_existing did not work: " + "did not remove %s with %s" % [is, should]) end FileUtils.rmtree(obj[:path]) # Now make it again creators[is].call state = obj.state(:ensure) state.retrieve unless state.insync? assert_nothing_raised do state.sync end end FileUtils.rmtree(dir) end end end end end # $Id$ diff --git a/test/types/filesources.rb b/test/types/filesources.rb index c1c601b59..09036cca0 100755 --- a/test/types/filesources.rb +++ b/test/types/filesources.rb @@ -1,940 +1,940 @@ #!/usr/bin/env ruby $:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ require 'puppet' require 'cgi' require 'fileutils' require 'puppettest' class TestFileSources < Test::Unit::TestCase include PuppetTest::FileTesting def setup super if defined? @port @port += 1 else @port = 8800 end @file = Puppet::Type.type(:file) end def use_storage begin initstorage rescue system("rm -rf %s" % Puppet[:statefile]) end end def initstorage Puppet::Storage.init Puppet::Storage.load end # Make a simple recursive tree. def mk_sourcetree source = tempfile() sourcefile = File.join(source, "file") Dir.mkdir source File.open(sourcefile, "w") { |f| f.puts "yay" } dest = tempfile() destfile = File.join(dest, "file") return source, dest, sourcefile, destfile end def test_newchild path = tempfile() @@tmpfiles.push path FileUtils.mkdir_p path File.open(File.join(path,"childtest"), "w") { |of| of.puts "yayness" } file = nil comp = nil trans = nil assert_nothing_raised { file = Puppet.type(:file).create( :name => path ) } child = nil assert_nothing_raised { child = file.newchild("childtest", true) } assert(child) assert_raise(Puppet::DevError) { file.newchild(File.join(path,"childtest"), true) } end def test_describe source = tempfile() dest = tempfile() file = Puppet::Type.newfile :path => dest, :source => source, :title => "copier" state = file.state(:source) # First try describing with a normal source result = nil assert_nothing_raised do result = state.describe(source) end assert_nil(result, "Got a result back when source is missing") # Now make a remote directory Dir.mkdir(source) assert_nothing_raised do result = state.describe(source) end assert_equal("directory", result[:type]) # And as a file Dir.rmdir(source) File.open(source, "w") { |f| f.puts "yay" } assert_nothing_raised do result = state.describe(source) end assert_equal("file", result[:type]) assert(result[:checksum], "did not get value for checksum") if Puppet::SUIDManager.uid == 0 - assert(result.has_key?("owner"), "Lost owner in describe") + assert(result.has_key?(:owner), "Lost owner in describe") else - assert(! result.has_key?("owner"), + assert(! result.has_key?(:owner), "Kept owner in describe even tho not root") end # Now let's do the various link things File.unlink(source) target = tempfile() File.open(target, "w") { |f| f.puts "yay" } File.symlink(target, source) file[:links] = :ignore assert_nil(state.describe(source), "Links were not ignored") file[:links] = :manage # We can't manage links at this point assert_raise(Puppet::FileServerError) do state.describe(source) end # And then make sure links get followed, otherwise file[:links] = :follow assert_equal("file", state.describe(source)[:type]) end def test_source_retrieve source = tempfile() dest = tempfile() file = Puppet::Type.newfile :path => dest, :source => source, :title => "copier" assert(file.state(:checksum), "source state did not create checksum state") state = file.state(:source) assert(state, "did not get source state") # Make sure the munge didn't actually change the source assert_equal([source], state.should, "munging changed the source") # First try it with a missing source assert_nothing_raised do state.retrieve end # And make sure the state considers itself in sync, since there's nothing # to do assert(state.insync?, "source thinks there's work to do with no file or dest") # Now make the dest a directory, and make sure the object sets :ensure up to # create a directory Dir.mkdir(source) assert_nothing_raised do state.retrieve end assert_equal(:directory, file.should(:ensure), "Did not set to create directory") # And make sure the source state won't try to do anything with a remote dir assert(state.insync?, "Source was out of sync even tho remote is dir") # Now remove the source, and make sure :ensure was not modified Dir.rmdir(source) assert_nothing_raised do state.retrieve end assert_equal(:directory, file.should(:ensure), "Did not keep :ensure setting") # Now have a remote file and make sure things work correctly File.open(source, "w") { |f| f.puts "yay" } File.chmod(0755, source) assert_nothing_raised do state.retrieve end assert_equal(:file, file.should(:ensure), "Did not make correct :ensure setting") assert_equal(0755, file.should(:mode), "Mode was not copied over") # Now let's make sure that we get the first found source fake = tempfile() state.should = [fake, source] assert_nothing_raised do state.retrieve end assert_equal(Digest::MD5.hexdigest(File.read(source)), state.checksum.sub(/^\{\w+\}/, ''), "Did not catch later source") end def test_insync source = tempfile() dest = tempfile() file = Puppet::Type.newfile :path => dest, :source => source, :title => "copier" state = file.state(:source) assert(state, "did not get source state") # Try it with no source at all file.retrieve assert(state.insync?, "source state not in sync with missing source") # with a directory Dir.mkdir(source) file.retrieve assert(state.insync?, "source state not in sync with directory as source") Dir.rmdir(source) # with a file File.open(source, "w") { |f| f.puts "yay" } file.retrieve assert(!state.insync?, "source state was in sync when file was missing") # With a different file File.open(dest, "w") { |f| f.puts "foo" } file.retrieve assert(!state.insync?, "source state was in sync with different file") # with matching files File.open(dest, "w") { |f| f.puts "yay" } file.retrieve assert(state.insync?, "source state was not in sync with matching file") end def test_source_sync source = tempfile() dest = tempfile() file = Puppet::Type.newfile :path => dest, :source => source, :title => "copier" state = file.state(:source) File.open(source, "w") { |f| f.puts "yay" } file.retrieve assert(! state.insync?, "source thinks it's in sync") event = nil assert_nothing_raised do event = state.sync end assert_equal(:file_created, event) assert_equal(File.read(source), File.read(dest), "File was not copied correctly") # Now write something different File.open(source, "w") { |f| f.puts "rah" } file.retrieve assert(! state.insync?, "source should be out of sync") assert_nothing_raised do event = state.sync end assert_equal(:file_changed, event) assert_equal(File.read(source), File.read(dest), "File was not copied correctly") end # XXX This test doesn't cover everything. Specifically, # it doesn't handle 'ignore' and 'links'. def test_sourcerecurse source, dest, sourcefile, destfile = mk_sourcetree # The sourcerecurse method will only ever get called when we're # recursing, so we go ahead and set it. obj = Puppet::Type.newfile :source => source, :path => dest, :recurse => true result = nil assert_nothing_raised do result = obj.sourcerecurse(true) end dfileobj = @file[destfile] assert(dfileobj, "Did not create destfile object") assert_equal([dfileobj], result) # Clean this up so it can be recreated dfileobj.remove # Make sure we correctly iterate over the sources nosource = tempfile() obj[:source] = [nosource, source] result = nil assert_nothing_raised do result = obj.sourcerecurse(true) end dfileobj = @file[destfile] assert(dfileobj, "Did not create destfile object with a missing source") assert_equal([dfileobj], result) dfileobj.remove # Lastly, make sure we return an empty array when no sources are there obj[:source] = [nosource, tempfile()] assert_nothing_raised do result = obj.sourcerecurse(true) end assert_equal([], result, "Sourcerecurse failed when all sources are missing") end def test_simplelocalsource path = tempfile() FileUtils.mkdir_p path frompath = File.join(path,"source") topath = File.join(path,"dest") fromfile = nil tofile = nil trans = nil File.open(frompath, File::WRONLY|File::CREAT|File::APPEND) { |of| of.puts "yayness" } assert_nothing_raised { tofile = Puppet.type(:file).create( :name => topath, :source => frompath ) } assert_apply(tofile) assert(FileTest.exists?(topath), "File #{topath} is missing") from = File.open(frompath) { |o| o.read } to = File.open(topath) { |o| o.read } assert_equal(from,to) end # Make sure a simple recursive copy works def test_simple_recursive_source source, dest, sourcefile, destfile = mk_sourcetree file = Puppet::Type.newfile :path => dest, :source => source, :recurse => true assert_events([:directory_created, :file_created], file) assert(FileTest.directory?(dest), "Dest dir was not created") assert(FileTest.file?(destfile), "dest file was not created") assert_equal("yay\n", File.read(destfile), "dest file was not copied correctly") end def recursive_source_test(fromdir, todir) Puppet::Type.allclear initstorage tofile = nil trans = nil assert_nothing_raised { tofile = Puppet.type(:file).create( :path => todir, :recurse => true, :backup => false, :source => fromdir ) } assert_apply(tofile) assert(FileTest.exists?(todir), "Created dir %s does not exist" % todir) Puppet::Type.allclear end def run_complex_sources(networked = false) path = tempfile() # first create the source directory FileUtils.mkdir_p path # okay, let's create a directory structure fromdir = File.join(path,"fromdir") Dir.mkdir(fromdir) FileUtils.cd(fromdir) { File.open("one", "w") { |f| f.puts "onefile"} File.open("two", "w") { |f| f.puts "twofile"} } todir = File.join(path, "todir") source = fromdir if networked source = "puppet://localhost/%s%s" % [networked, fromdir] end recursive_source_test(source, todir) return [fromdir,todir, File.join(todir, "one"), File.join(todir, "two")] end def test_complex_sources_twice fromdir, todir, one, two = run_complex_sources assert_trees_equal(fromdir,todir) recursive_source_test(fromdir, todir) assert_trees_equal(fromdir,todir) # Now remove the whole tree and try it again. [one, two].each do |f| File.unlink(f) end Dir.rmdir(todir) recursive_source_test(fromdir, todir) assert_trees_equal(fromdir,todir) end def test_sources_with_deleted_destfiles fromdir, todir, one, two = run_complex_sources assert(FileTest.exists?(todir)) # We shouldn't have a 'two' file object in memory assert_nil(@file[two], "object for 'two' is still in memory") # then delete a file File.unlink(two) # and run recursive_source_test(fromdir, todir) assert(FileTest.exists?(two), "Deleted file was not recopied") # and make sure they're still equal assert_trees_equal(fromdir,todir) end def test_sources_with_readonly_destfiles fromdir, todir, one, two = run_complex_sources assert(FileTest.exists?(todir)) File.chmod(0600, one) recursive_source_test(fromdir, todir) # and make sure they're still equal assert_trees_equal(fromdir,todir) # Now try it with the directory being read-only File.chmod(0111, todir) recursive_source_test(fromdir, todir) # and make sure they're still equal assert_trees_equal(fromdir,todir) end def test_sources_with_modified_dest_files fromdir, todir, one, two = run_complex_sources assert(FileTest.exists?(todir)) # Modify a dest file File.open(two, "w") { |f| f.puts "something else" } recursive_source_test(fromdir, todir) # and make sure they're still equal assert_trees_equal(fromdir,todir) end def test_sources_with_added_destfiles fromdir, todir = run_complex_sources assert(FileTest.exists?(todir)) # and finally, add some new files add_random_files(todir) recursive_source_test(fromdir, todir) fromtree = file_list(fromdir) totree = file_list(todir) assert(fromtree != totree, "Trees are incorrectly equal") # then remove our new files FileUtils.cd(todir) { %x{find . 2>/dev/null}.chomp.split(/\n/).each { |file| if file =~ /file[0-9]+/ File.unlink(file) end } } # and make sure they're still equal assert_trees_equal(fromdir,todir) end # Make sure added files get correctly caught during recursion def test_RecursionWithAddedFiles basedir = tempfile() Dir.mkdir(basedir) @@tmpfiles << basedir file1 = File.join(basedir, "file1") file2 = File.join(basedir, "file2") subdir1 = File.join(basedir, "subdir1") file3 = File.join(subdir1, "file") File.open(file1, "w") { |f| f.puts "yay" } rootobj = nil assert_nothing_raised { rootobj = Puppet.type(:file).create( :name => basedir, :recurse => true, :check => %w{type owner}, :mode => 0755 ) } assert_apply(rootobj) assert_equal(0755, filemode(file1)) File.open(file2, "w") { |f| f.puts "rah" } assert_apply(rootobj) assert_equal(0755, filemode(file2)) Dir.mkdir(subdir1) File.open(file3, "w") { |f| f.puts "foo" } assert_apply(rootobj) assert_equal(0755, filemode(file3)) end def mkfileserverconf(mounts) file = tempfile() File.open(file, "w") { |f| mounts.each { |path, name| f.puts "[#{name}]\n\tpath #{path}\n\tallow *\n" } } @@tmpfiles << file return file end def test_NetworkSources server = nil mounts = { "/" => "root" } fileserverconf = mkfileserverconf(mounts) Puppet[:autosign] = true Puppet[:masterport] = 8762 serverpid = nil assert_nothing_raised() { server = Puppet::Server.new( :Handlers => { :CA => {}, # so that certs autogenerate :FileServer => { :Config => fileserverconf } } ) } serverpid = fork { assert_nothing_raised() { #trap(:INT) { server.shutdown; Kernel.exit! } trap(:INT) { server.shutdown } server.start } } @@tmppids << serverpid sleep(1) fromdir, todir = run_complex_sources("root") assert_trees_equal(fromdir,todir) recursive_source_test(fromdir, todir) assert_trees_equal(fromdir,todir) assert_nothing_raised { system("kill -INT %s" % serverpid) } end def test_networkSourcesWithoutService server = nil Puppet[:autosign] = true Puppet[:masterport] = 8765 serverpid = nil assert_nothing_raised() { server = Puppet::Server.new( :Handlers => { :CA => {}, # so that certs autogenerate } ) } serverpid = fork { assert_nothing_raised() { #trap(:INT) { server.shutdown; Kernel.exit! } trap(:INT) { server.shutdown } server.start } } @@tmppids << serverpid sleep(1) name = File.join(tmpdir(), "nosourcefile") file = Puppet.type(:file).create( :source => "puppet://localhost/dist/file", :name => name ) assert_nothing_raised { file.retrieve } comp = newcomp("nosource", file) assert_nothing_raised { comp.evaluate } assert(!FileTest.exists?(name), "File with no source exists anyway") end def test_unmountedNetworkSources server = nil mounts = { "/" => "root", "/noexistokay" => "noexist" } fileserverconf = mkfileserverconf(mounts) Puppet[:autosign] = true Puppet[:masterport] = @port serverpid = nil assert_nothing_raised() { server = Puppet::Server.new( :Port => @port, :Handlers => { :CA => {}, # so that certs autogenerate :FileServer => { :Config => fileserverconf } } ) } serverpid = fork { assert_nothing_raised() { #trap(:INT) { server.shutdown; Kernel.exit! } trap(:INT) { server.shutdown } server.start } } @@tmppids << serverpid sleep(1) name = File.join(tmpdir(), "nosourcefile") file = Puppet.type(:file).create( :source => "puppet://localhost/noexist/file", :name => name ) assert_nothing_raised { file.retrieve } comp = newcomp("nosource", file) assert_nothing_raised { comp.evaluate } assert(!FileTest.exists?(name), "File with no source exists anyway") end def test_alwayschecksum from = tempfile() to = tempfile() File.open(from, "w") { |f| f.puts "yayness" } File.open(to, "w") { |f| f.puts "yayness" } file = nil # Now the files should be exactly the same, so we should not see attempts # at copying assert_nothing_raised { file = Puppet.type(:file).create( :path => to, :source => from ) } file.retrieve assert(file.is(:checksum), "File does not have a checksum state") assert_equal(0, file.evaluate.length, "File produced changes") end def test_sourcepaths files = [] 3.times { files << tempfile() } to = tempfile() File.open(files[-1], "w") { |f| f.puts "yee-haw" } file = nil assert_nothing_raised { file = Puppet.type(:file).create( :name => to, :source => files ) } comp = newcomp(file) assert_events([:file_created], comp) assert(File.exists?(to), "File does not exist") txt = nil File.open(to) { |f| txt = f.read.chomp } assert_equal("yee-haw", txt, "Contents do not match") end # Make sure that source-copying updates the checksum on the same run def test_checksumchange source = tempfile() dest = tempfile() File.open(dest, "w") { |f| f.puts "boo" } File.open(source, "w") { |f| f.puts "yay" } file = nil assert_nothing_raised { file = Puppet.type(:file).create( :name => dest, :source => source ) } file.retrieve assert_events([:file_changed], file) file.retrieve assert_events([], file) end # Make sure that source-copying updates the checksum on the same run def test_sourcebeatsensure source = tempfile() dest = tempfile() File.open(source, "w") { |f| f.puts "yay" } file = nil assert_nothing_raised { file = Puppet.type(:file).create( :name => dest, :ensure => "file", :source => source ) } file.retrieve assert_events([:file_created], file) file.retrieve assert_events([], file) assert_events([], file) end def test_sourcewithlinks source = tempfile() link = tempfile() dest = tempfile() File.open(source, "w") { |f| f.puts "yay" } File.symlink(source, link) file = nil assert_nothing_raised { file = Puppet.type(:file).create( :name => dest, :source => link ) } # Default to skipping links assert_events([], file) assert(! FileTest.exists?(dest), "Created link") # Now follow the links file[:links] = :follow assert_events([:file_created], file) assert(FileTest.file?(dest), "Destination is not a file") # Now copy the links #assert_raise(Puppet::FileServerError) { trans = nil assert_nothing_raised { file[:links] = :manage comp = newcomp(file) trans = comp.evaluate trans.evaluate } assert(trans.failed?(file), "Object did not fail to copy links") end def test_changes source = tempfile() dest = tempfile() File.open(source, "w") { |f| f.puts "yay" } obj = nil assert_nothing_raised { obj = Puppet.type(:file).create( :name => dest, :source => source ) } assert_events([:file_created], obj) assert_equal(File.read(source), File.read(dest), "Files are not equal") assert_events([], obj) File.open(source, "w") { |f| f.puts "boo" } assert_events([:file_changed], obj) assert_equal(File.read(source), File.read(dest), "Files are not equal") assert_events([], obj) File.open(dest, "w") { |f| f.puts "kaboom" } # There are two changes, because first the checksum is noticed, and # then the source causes a change assert_events([:file_changed, :file_changed], obj) assert_equal(File.read(source), File.read(dest), "Files are not equal") assert_events([], obj) end def test_file_source_with_space dir = tempfile() source = File.join(dir, "file with spaces") Dir.mkdir(dir) File.open(source, "w") { |f| f.puts "yayness" } newdir = tempfile() newpath = File.join(newdir, "file with spaces") file = Puppet::Type.newfile( :path => newdir, :source => dir, :recurse => true ) assert_apply(file) assert(FileTest.exists?(newpath), "Did not create file") assert_equal("yayness\n", File.read(newpath)) end # Make sure files aren't replaced when replace is false, but otherwise # are. def test_replace source = tempfile() File.open(source, "w") { |f| f.puts "yayness" } dest = tempfile() file = Puppet::Type.newfile( :path => dest, :source => source, :recurse => true ) assert_apply(file) assert(FileTest.exists?(dest), "Did not create file") assert_equal("yayness\n", File.read(dest)) # Now set :replace assert_nothing_raised { file[:replace] = false } File.open(source, "w") { |f| f.puts "funtest" } assert_apply(file) # Make sure it doesn't change. assert_equal("yayness\n", File.read(dest), "File got replaced when :replace was false") # Now set it to true and make sure it does change. assert_nothing_raised { file[:replace] = true } assert_apply(file) # Make sure it doesn't change. assert_equal("funtest\n", File.read(dest), "File was not replaced when :replace was true") end # Testing #285. This just makes sure that URI parsing works correctly. def test_fileswithpoundsigns dir = tstdir() subdir = File.join(dir, "#dir") Dir.mkdir(subdir) file = File.join(subdir, "file") File.open(file, "w") { |f| f.puts "yayness" } dest = tempfile() source = "file://localhost#{dir}" obj = Puppet::Type.newfile( :path => dest, :source => source, :recurse => true ) newfile = File.join(dest, "#dir", "file") poundsource = "file://localhost#{subdir}" sourceobj = path = nil assert_nothing_raised { sourceobj, path = obj.uri2obj(poundsource) } assert_equal("/localhost" + URI.escape(subdir), path) assert_apply(obj) assert(FileTest.exists?(newfile), "File did not get created") assert_equal("yayness\n", File.read(newfile)) end end # $Id$ diff --git a/test/types/host.rb b/test/types/host.rb index a68e46020..280959c45 100755 --- a/test/types/host.rb +++ b/test/types/host.rb @@ -1,137 +1,155 @@ #!/usr/bin/env ruby $:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ require 'puppettest' require 'puppet' require 'test/unit' require 'facter' class TestHost < Test::Unit::TestCase include PuppetTest def setup super @hosttype = Puppet.type(:host) @provider = @hosttype.defaultprovider # Make sure they aren't using something funky like netinfo unless @provider.name == :parsed @hosttype.defaultprovider = @hosttype.provider(:parsed) end cleanup do @hosttype.defaultprovider = nil end if @provider.respond_to?(:default_target=) @default_file = @provider.default_target cleanup do @provider.default_target = @default_file end @target = tempfile() @provider.default_target = @target end end def mkhost if defined? @hcount @hcount += 1 else @hcount = 1 end host = nil assert_nothing_raised { host = Puppet.type(:host).create( :name => "fakehost%s" % @hcount, :ip => "192.168.27.%s" % @hcount, :alias => "alias%s" % @hcount ) } return host end def test_list assert_nothing_raised do @hosttype.defaultprovider.prefetch end count = 0 @hosttype.each do |h| count += 1 end assert_equal(0, count, "Found hosts in empty file somehow") end # Darwin will actually write to netinfo here. if Facter.value(:operatingsystem) != "Darwin" or Process.uid == 0 def test_simplehost host = nil assert_nothing_raised { host = Puppet.type(:host).create( :name => "culain", :ip => "192.168.0.3" ) } assert_events([:host_created], host) assert_nothing_raised { host.retrieve } assert_equal(:present, host.is(:ensure)) host[:ensure] = :absent assert_events([:host_deleted], host) assert_nothing_raised { host.retrieve } assert_equal(:absent, host.is(:ensure)) end def test_moddinghost + # We want to actually use the netinfo provider on darwin + if Facter.value(:operatingsystem) == "Darwin" + Puppet::Type.type(:host).defaultprovider = nil + end host = mkhost() + if Facter.value(:operatingsystem) == "Darwin" + assert_equal(:netinfo, host[:provider], "Got incorrect provider") + end + cleanup do + host[:ensure] = :absent + assert_apply(host) + end assert_events([:host_created], host) host.retrieve # This was a hard bug to track down. assert_instance_of(String, host.is(:ip)) host[:alias] = %w{madstop kirby yayness} assert_events([:host_changed], host) host.retrieve - assert_equal(%w{madstop kirby yayness}, host.is(:alias)) + if Facter.value(:operatingsystem) == "Darwin" + # Netinfo can't handle arrays right now + assert_equal(%w{madstop}, host.is(:alias)) + else + assert_equal(%w{madstop kirby yayness}, host.is(:alias)) + end + host[:ensure] = :absent + assert_events([:host_removed], host) end end def test_aliasisstate assert_equal(:state, @hosttype.attrtype(:alias)) end def test_multivalues host = mkhost assert_raise(Puppet::Error) { host[:alias] = "puppetmasterd yayness" } end def test_puppetalias host = mkhost() assert_nothing_raised { host[:alias] = "testing" } same = host.class["testing"] assert(same, "Could not retrieve by alias") end end # $Id$ diff --git a/test/types/type.rb b/test/types/type.rb index aa37f2a18..e8a4d45f1 100755 --- a/test/types/type.rb +++ b/test/types/type.rb @@ -1,779 +1,780 @@ #!/usr/bin/env ruby $:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ require 'puppet/type' require 'puppettest' class TestType < Test::Unit::TestCase include PuppetTest def test_typemethods Puppet::Type.eachtype { |type| name = nil assert_nothing_raised("Searching for name for %s caused failure" % type.to_s) { name = type.name } assert(name, "Could not find name for %s" % type.to_s) assert_equal( type, Puppet::Type.type(name), "Failed to retrieve %s by name" % name ) # Skip types with no parameters or valid states #unless ! type.parameters.empty? or ! type.validstates.empty? # next #end assert_nothing_raised { assert( type.namevar, "Failed to retrieve namevar for %s" % name ) assert_not_nil( type.states, "States for %s are nil" % name ) assert_not_nil( type.validstates, "Valid states for %s are nil" % name ) } } end def test_stringvssymbols file = nil path = tempfile() assert_nothing_raised() { system("rm -f %s" % path) file = Puppet.type(:file).create( :path => path, :ensure => "file", :recurse => true, :checksum => "md5" ) } assert_nothing_raised() { file.retrieve } assert_nothing_raised() { file.evaluate } Puppet.type(:file).clear assert_nothing_raised() { system("rm -f %s" % path) file = Puppet.type(:file).create( "path" => path, "ensure" => "file", "recurse" => true, "checksum" => "md5" ) } assert_nothing_raised() { file.retrieve } assert_nothing_raised() { file[:path] } assert_nothing_raised() { file["path"] } assert_nothing_raised() { file[:recurse] } assert_nothing_raised() { file["recurse"] } assert_nothing_raised() { file.evaluate } end # This was supposed to test objects whose name was a state, but that # fundamentally doesn't make much sense, and we now don't have any such # types. def disabled_test_nameasstate # currently groups are the only objects with the namevar as a state group = nil assert_nothing_raised { group = Puppet.type(:group).create( :name => "testing" ) } assert_equal("testing", group.name, "Could not retrieve name") end # Verify that values get merged correctly def test_mergestatevalues file = tempfile() # Create the first version assert_nothing_raised { Puppet.type(:file).create( :path => file, :owner => ["root", "bin"] ) } # Make sure no other statements are allowed assert_raise(Puppet::Error) { Puppet.type(:file).create( :path => file, :group => "root" ) } end # Verify that aliasing works def test_aliasing file = tempfile() baseobj = nil assert_nothing_raised { baseobj = Puppet.type(:file).create( :name => file, :ensure => "file", :alias => ["funtest"] ) } # Verify our adding ourselves as an alias isn't an error. assert_nothing_raised { baseobj[:alias] = file } assert_instance_of(Puppet.type(:file), Puppet.type(:file)["funtest"], "Could not retrieve alias") end # Verify that requirements don't depend on file order def test_prereqorder one = tempfile() two = tempfile() twoobj = nil oneobj = nil assert_nothing_raised("Could not create prereq that doesn't exist yet") { twoobj = Puppet.type(:file).create( :name => two, :require => [:file, one] ) } assert_nothing_raised { oneobj = Puppet.type(:file).create( :name => one ) } comp = newcomp(twoobj, oneobj) assert_nothing_raised { comp.finalize } assert(twoobj.requires?(oneobj), "Requirement was not created") end # Verify that names are aliases, not equivalents def test_nameasalias file = nil # Create the parent dir, so we make sure autorequiring the parent dir works parentdir = tempfile() dir = Puppet.type(:file).create( :name => parentdir, :ensure => "directory" ) assert_apply(dir) path = File.join(parentdir, "subdir") name = "a test file" transport = Puppet::TransObject.new(name, "file") transport[:path] = path transport[:ensure] = "file" assert_nothing_raised { file = transport.to_type } assert_equal(path, file[:path]) assert_equal(name, file.title) assert_nothing_raised { file.retrieve } assert_apply(file) assert(Puppet.type(:file)[name], "Could not look up object by name") end def test_ensuredefault user = nil assert_nothing_raised { user = Puppet.type(:user).create( :name => "pptestAA", :check => [:uid] ) } # make sure we don't get :ensure for unmanaged files assert(! user.state(:ensure), "User got an ensure state") assert_nothing_raised { user = Puppet.type(:user).create( :name => "pptestAA", :comment => "Testingness" ) } # but make sure it gets added once we manage them assert(user.state(:ensure), "User did not add ensure state") assert_nothing_raised { user = Puppet.type(:user).create( :name => "pptestBB", :comment => "A fake user" ) } # and make sure managed objects start with them assert(user.state(:ensure), "User did not get an ensure state") end # Make sure removal works def test_remove objects = {} top = Puppet.type(:component).create(:name => "top") objects[top.class] = top base = tempfile() # now make a two-tier, 5 piece tree %w{a b}.each do |letter| name = "comp%s" % letter comp = Puppet.type(:component).create(:name => name) top.push comp objects[comp.class] = comp 5.times do |i| file = base + letter + i.to_s obj = Puppet.type(:file).create(:name => file, :ensure => "file") comp.push obj objects[obj.class] = obj end end assert_nothing_raised do top.remove end objects.each do |klass, obj| assert_nil(klass[obj.name], "object %s was not removed" % obj.name) end end # Verify that objects can't be their own children. def test_object_recursion comp = Puppet.type(:component).create(:name => "top") file = Puppet.type(:file).create(:path => tempfile, :ensure => :file) assert_raise(Puppet::DevError) do comp.push(comp) end assert_raise(Puppet::DevError) do file.push(file) end assert_raise(Puppet::DevError) do comp.parent = comp end assert_raise(Puppet::DevError) do file.parent = file end assert_nothing_raised { comp.push(file) } assert_raise(Puppet::DevError) do file.push(comp) end assert_raise(Puppet::DevError) do comp.parent = file end end def test_loadplugins names = %w{loadedplugin1 loadplugin2 loadplugin3} dirs = [] 3.times { dirs << tempfile() } # Set plugindest to something random Puppet[:plugindest] = tempfile() Puppet[:pluginpath] = dirs.join(":") names.each do |name| dir = dirs.shift Dir.mkdir(dir) # Create an extra file for later [name, name + "2ness"].each do |n| file = File.join(dir, n + ".rb") File.open(file, "w") do |f| f.puts %{Puppet::Type.newtype('#{n}') do newparam(:argument) do isnamevar end end } end end assert(Puppet::Type.type(name), "Did not get loaded plugin") assert_nothing_raised { Puppet::Type.type(name).create( :name => "myname" ) } end # Now make sure the plugindest got added to our pluginpath assert(Puppet[:pluginpath].split(":").include?(Puppet[:plugindest]), "Plugin dest did not get added to plugin path") # Now make sure it works with just a single path, using the extra files # created above. Puppet[:pluginpath] = Puppet[:pluginpath].split(":")[0] assert(Puppet::Type.type("loadedplugin12ness"), "Did not get loaded plugin") end def test_newtype_methods assert_nothing_raised { Puppet::Type.newtype(:mytype) do newparam(:wow) do isnamevar end end } assert(Puppet::Type.respond_to?(:newmytype), "new method did not get created") obj = nil assert_nothing_raised { obj = Puppet::Type.newmytype(:wow => "yay") } assert(obj.is_a?(Puppet::Type.type(:mytype)), "Obj is not the correct type") # Now make the type again, just to make sure it works on refreshing. assert_nothing_raised { Puppet::Type.newtype(:mytype) do newparam(:yay) do isnamevar end end } obj = nil # Make sure the old class was thrown away and only the new one is sitting # around. assert_raise(Puppet::Error) { obj = Puppet::Type.newmytype(:wow => "yay") } assert_nothing_raised { obj = Puppet::Type.newmytype(:yay => "yay") } # Now make sure that we don't replace existing, non-type methods parammethod = Puppet::Type.method(:newparam) assert_nothing_raised { Puppet::Type.newtype(:param) do newparam(:rah) do isnamevar end end } assert_equal(parammethod, Puppet::Type.method(:newparam), "newparam method got replaced by newtype") end def test_newstate_options # Create a type with a fake provider providerclass = Class.new do def method_missing(method, *args) return method end end self.class.const_set("ProviderClass", providerclass) type = Puppet::Type.newtype(:mytype) do newparam(:name) do isnamevar end def provider @provider ||= ProviderClass.new @provider end end # Now make a state with no options. state = nil assert_nothing_raised do state = type.newstate(:noopts) do end end # Now create an instance obj = type.create(:name => :myobj) inst = state.new(:parent => obj) # And make sure it's correctly setting @is ret = nil assert_nothing_raised { ret = inst.retrieve } assert_equal(:noopts, inst.is) # Now create a state with a different way of doing it state = nil assert_nothing_raised do state = type.newstate(:setretrieve, :retrieve => :yayness) end inst = state.new(:parent => obj) # And make sure it's correctly setting @is ret = nil assert_nothing_raised { ret = inst.retrieve } assert_equal(:yayness, inst.is) end def test_name_vs_title path = tempfile() trans = nil assert_nothing_raised { trans = Puppet::TransObject.new(path, :file) } file = nil assert_nothing_raised { file = Puppet::Type.newfile(trans) } assert(file.respond_to?(:title), "No 'title' method") assert(file.respond_to?(:name), "No 'name' method") assert_equal(file.title, file.name, "Name and title were not marked equal") assert_nothing_raised { file.title = "My file" } assert_equal("My file", file.title) assert_equal(path, file.name) end # Make sure the title is sufficiently differentiated from the namevar. def test_title_at_creation_with_hash file = nil fileclass = Puppet::Type.type(:file) path = tempfile() assert_nothing_raised do file = fileclass.create( :title => "Myfile", :path => path ) end assert_equal("Myfile", file.title, "Did not get correct title") assert_equal(path, file[:name], "Did not get correct name") file = nil Puppet::Type.type(:file).clear # Now make sure we can specify both and still get the right answers assert_nothing_raised do file = fileclass.create( :title => "Myfile", :name => path ) end assert_instance_of(fileclass, file) assert_equal("Myfile", file.title, "Did not get correct title") assert_equal(path, file[:name], "Did not get correct name") end # Make sure the "create" class method behaves appropriately. def test_class_create title = "Myfile" validate = proc do |element| assert(element, "Did not create file") assert_instance_of(Puppet::Type.type(:file), element) assert_equal(title, element.title, "Title is not correct") end type = :file args = {:path => tempfile(), :owner => "root"} trans = Puppet::TransObject.new(title, type) args.each do |name, val| trans[name] = val end # First call it on the appropriate typeclass obj = nil assert_nothing_raised do obj = Puppet::Type.type(:file).create(trans) end validate.call(obj) # Now try it using the class method on Type oldid = obj.object_id obj = nil Puppet::Type.type(:file).clear assert_nothing_raised { obj = Puppet::Type.create(trans) } validate.call(obj) assert(oldid != obj.object_id, "Got same object back") # Now try the same things with hashes instead of a transobject oldid = obj.object_id obj = nil Puppet::Type.type(:file).clear hash = { :type => :file, :title => "Myfile", :path => tempfile(), :owner => "root" } # First call it on the appropriate typeclass obj = nil assert_nothing_raised do obj = Puppet::Type.type(:file).create(hash) end validate.call(obj) assert_equal(:file, obj.should(:type), "Type param did not pass through") assert(oldid != obj.object_id, "Got same object back") # Now try it using the class method on Type oldid = obj.object_id obj = nil Puppet::Type.type(:file).clear assert_nothing_raised { obj = Puppet::Type.create(hash) } validate.call(obj) assert(oldid != obj.object_id, "Got same object back") assert_nil(obj.should(:type), "Type param passed through") end def test_multiplenames obj = nil path = tempfile() assert_raise ArgumentError do obj = Puppet::Type.type(:file).create( :name => path, :path => path ) end end def test_title_and_name obj = nil path = tempfile() fileobj = Puppet::Type.type(:file) assert_nothing_raised do obj = fileobj.create( :title => "myfile", :path => path ) end assert_equal(obj, fileobj["myfile"], "Could not retrieve obj by title") assert_equal(obj, fileobj[path], "Could not retrieve obj by name") end # Make sure default providers behave correctly def test_defaultproviders # Make a fake type type = Puppet::Type.newtype(:defaultprovidertest) do newparam(:name) do end end basic = type.provide(:basic) do defaultfor :operatingsystem => :somethingelse, :operatingsystemrelease => :yayness end assert_equal(basic, type.defaultprovider) type.defaultprovider = nil greater = type.provide(:greater) do defaultfor :operatingsystem => Facter.value("operatingsystem") end assert_equal(greater, type.defaultprovider) end # Make sure that we can have multiple isomorphic objects with the same name, # but not with non-isomorphic objects. def test_isomorphic_names # First do execs, since they're not isomorphic. echo = Puppet::Util.binary "echo" exec1 = exec2 = nil assert_nothing_raised do exec1 = Puppet::Type.type(:exec).create( :title => "exec1", :command => "#{echo} funtest" ) end assert_nothing_raised do exec2 = Puppet::Type.type(:exec).create( :title => "exec2", :command => "#{echo} funtest" ) end assert_apply(exec1, exec2) # Now do files, since they are. This should fail. file1 = file2 = nil path = tempfile() assert_nothing_raised do file1 = Puppet::Type.type(:file).create( :title => "file1", :path => path, :content => "yayness" ) end # This will fail, but earlier systems will catch it. assert_raise(Puppet::Error) do file2 = Puppet::Type.type(:file).create( :title => "file2", :path => path, :content => "rahness" ) end assert(file1, "Did not create first file") assert_nil(file2, "Incorrectly created second file") end def test_tags obj = Puppet::Type.type(:file).create(:path => tempfile()) tags = [:some, :test, :tags] obj.tags = tags assert_equal(tags + [:file], obj.tags) end def disabled_test_list Puppet::Type.loadall Puppet::Type.eachtype do |type| next if type.name == :symlink next if type.name == :component next if type.name == :tidy assert(type.respond_to?(:list), "%s does not respond to list" % type.name) end end def test_to_hash file = Puppet::Type.newfile :path => tempfile(), :owner => "luke", :recurse => true, :loglevel => "warning" hash = nil assert_nothing_raised do hash = file.to_hash end [:path, :owner, :recurse, :loglevel].each do |param| assert(hash[param], "Hash did not include %s" % param) end end # Make sure that classes behave like hashes. def test_class_hash_behaviour path = tempfile() filetype = Puppet::Type.type(:file) one = Puppet::Type.newfile :path => path assert_equal(one, filetype[path], "Did not get file back") assert_raise(Puppet::Error) do filetype[path] = one end end def test_ref path = tempfile() + Puppet::Type.type(:exec) # uggh, the methods need to load the types file = Puppet::Type.newfile(:path => path) assert_equal("File[#{path}]", file.ref) exec = Puppet::Type.newexec(:title => "yay", :command => "/bin/echo yay") - assert_equal("exec[yay]", exec.ref) + assert_equal("Exec[yay]", exec.ref) end def test_noop_metaparam file = Puppet::Type.newfile :path => tempfile assert(!file.noop, "file incorrectly in noop") assert_nothing_raised do file[:noop] = true end assert(file.noop, "file should be in noop") # Now set the main one Puppet[:noop] = true assert(file.noop, "file should be in noop") file[:noop] = false assert(file.noop, "file should be in noop") end end # $Id$