diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index dd8e89720..a9a78744a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,299 +1,242 @@ -Checklist (and a short version for the impatient) +Checklist/Outline (The short version) ================================================= - * Commits: - - - Make commits of logical units. - - - Check for unnecessary whitespace with "git diff --check" before - committing. - - - Commit using Unix line endings (check the settings around "crlf" in - git-config(1)). - - - Do not check in commented out code or unneeded files. - - - The first line of the commit message should be a short - description (50 characters is the soft limit, excluding ticket - number(s)), and should skip the full stop. - - - If there is an associated Redmine ticket then the first line - should include the ticket number in the form "(#XXXX) Rest of - message". - - - The body should provide a meaningful commit message, which: - - - uses the imperative, present tense: "change", not "changed" or - "changes". - - - includes motivation for the change, and contrasts its - implementation with the previous behavior. - - - Make sure that you have tests for the bug you are fixing, or - feature you are adding. - - - Make sure the test suite passes after your commit (rake spec unit). - - * Submission: - - * Pre-requisites: - - - Make sure you have a [Redmine account](http://projects.puppetlabs.com) - - - Sign the [Contributor License Agreement](https://projects.puppetlabs.com/contributor_licenses/sign) - - * Preferred method: - - - Fork the repository on GitHub. - - - Push your changes to a topic branch in your fork of the - repository. - - - Submit a pull request to the repository in the puppetlabs - organization. - - * Alternate methods: - - - Mail patches to puppet-dev mailing list using `rake mail_patches`, - or `git-format-patch(1)` & `git-send-email(1)`. - - - Attach patches to Redmine ticket. - + * Getting Started: + - Make sure you have a [Redmine account](http://projects.puppetlabs.com) + - Submit a ticket for your issue, assuming one does not already exist. + - Decide what to base your work off of + * `2.6.x`: security fixes only + * `2.7.x`: bug fixes only + * `3.x`: new features that are not breaking changes + * `master`: new features that are breaking changes + + * Making Changes: + - Make sure you have a [GitHub account](https://github.com/signup/free) + - Fork the repository on GitHub + - Make commits of logical units. + - Check for unnecessary whitespace with "git diff --check" before committing. + - Make sure your commit messages are in the proper format + - Make sure you have added the necessary tests for your changes + - Run _all_ the tests to assure nothing else was accidentally broken + + * Submitting Changes: + - Sign the [Contributor License Agreement](https://projects.puppetlabs.com/contributor_licenses/sign) + - Push your changes to a topic branch in your fork of the repository. + - Submit a pull request to the repository in the puppetlabs organization. + - Update your Redmine ticket + The long version ================ - 0. Decide what to base your work on. - - In general, you should always base your work on the oldest - branch that your change is relevant to. - - - A bug fix should be based on the current stable series. If the - bug is not present in the current stable release, then base it on - `master`. - - - A new feature should be based on `master`. - - - Security fixes should be based on the current maintenance series - (that is, the previous stable series). If the security issue - was not present in the maintenance series, then it should be - based on the current stable series if it was introduced there, - or on `master` if it is not yet present in a stable release. - - The current stable series is 2.7.x, and the current maintenance - series is 2.6.x. - - 1. Make separate commits for logically separate changes. + 0. Create a Redmine ticket for the change you'd like to make. + + It's very important that there be a Redmine ticket for the change + you are making. Considering the number of contributions which are + submitted, it is crucial that we know we can find the ticket on Redmine. + + Before making a ticket however, be sure that one does not already exist. + You can do this by searching Redmine or by trying a Google search which + includes `sites:projects.puppetlabs.com` in addition to some of the keywords + related to your issue. + + If you do not find a ticket that that accurately describes the work + you're going to be doing, go ahead and create one. But be sure to + look for related tickets and add them to the 'related tickets' section. + + 1. Decide what to base your work on. + + In general, you should always base your work on the oldest + branch that your change is relevant to, and it will be + eventually merged up. Currently, branches will be merged up as + follows: + 2.6.x => 2.7.x => 3.x => master + + Currently, this is how you should decide where to target your changes: + + The absolute earliest place something should be targeted is at `2.6.x`, + and these should _only_ be security fixes. Anything else must be + targeted at a later branch. + + A bug fix should be based off the the earliest place where it is + relevant. If it first appears in `2.7.x`, then it should be + targeted here and eventually merged up to `3.x` and master. + + New features which are _backwards compatible_ should be targeted + at the next release, which currently is `3.x`. + + New features that are _breaking changes_ should be targeted at + `master`. + + Part of deciding what to what your work should be based off of includes naming + your topic branch to reflect this. Your branch name should have the following + format: + `ticket/target_branch/ticket_number_short_description_of_issuee` + + For example, if you are fixing a bug relating to the ssl spec, which has Redmine + ticket number 12345, then your branch should be named: + `ticket/2.7.x/12345_fix_ssl_spec_tests` + + There is a good chance that if you submit a pull request _from_ master _to_ master, + Puppet Labs developers will suspect that you're not sure about the process. This is + why clear naming of branches and basing your work off the right place will be + extremely helpful in ensuring that your submission is reviewed and merged. Often times + if your change is targeted at the wrong place, we will bounce it back to you and wait + to review it until it has been retargeted. + + 2. Make separate commits for logically separate changes. Please break your commits down into logically consistent units which include new or changed tests relevent to the rest of the change. The goal of doing this is to make the diff easier to read for whoever is reviewing your code. In general, the easier your diff is to read, the more likely someone will be happy to review it and get it into the code base. If you're going to refactor a piece of code, please do so as a separate commit from your feature or bug fix changes. - We also really appreciate changes that include tests to make + It's crucial that your changes include tests to make sure the bug isn't re-introduced, and that the feature isn't accidentally broken. Describe the technical detail of the change(s). If your description starts to get too long, that's a good sign that you probably need to split up your commit into more finely grained pieces. Commits which plainly describe the the things which help reviewers check the patch and future developers understand the code are much more likely to be merged in with a minimum of bike-shedding or requested changes. Ideally, the commit message would include information, and be in a form suitable for inclusion in the release notes for the version of Puppet that includes them. Please also check that you are not introducing any trailing whitespaces or other "whitespace errors". You can do this by running "git diff --check" on your changes before you commit. - 2. Sign the Contributor License Agreement + When writing commit messages, please be sure they meet + [these standards](https://github.com/erlang/otp/wiki/Writing-good-commit-messages), and please include the ticket number in your + short summary. It should look something like this: `(#12345) Fix this issue in Puppet` + + 3. Sign the Contributor License Agreement Before we can accept your changes, we do need a signed Puppet Labs Contributor License Agreement (CLA). You can access the CLA via the [Contributor License Agreement link](https://projects.puppetlabs.com/contributor_licenses/sign) in the top menu bar of our Redmine instance. Once you've signed the CLA, a badge will show up next to your name on the [Puppet Project Overview Page](http://projects.puppetlabs.com/projects/puppet?jump=welcome), and your name will be listed under "Contributor License Signers" section. If you have any questions about the CLA, please feel free to contact Puppet Labs via email at cla-submissions@puppetlabs.com. - 3. Sending your patches - - We accept multiple ways of submitting your changes for - inclusion. They are listed below in order of preference. - - Please keep in mind that any method that involves sending email - to the mailing list directly requires you to be subscribed to - the mailing list, and that your first post to the list will be - held in a moderation queue. - - * GitHub Pull Requests - - To submit your changes via a GitHub pull request, we _highly_ - recommend that you have them on a topic branch, instead of - directly on "master" or one of the release, or RC branches. - It makes things much easier to keep track of, especially if - you decide to work on another thing before your first change - is merged in. + 4. Sending your patches - GitHub has some pretty good - [general documentation](http://help.github.com/) on using - their site. They also have documentation on - [creating pull requests](http://help.github.com/send-pull-requests/). + To submit your changes via a GitHub pull request, you must + have them on a topic branch, instead of directly on "master" + or one of the release, or RC branches. It makes things much easier + to keep track of, especially if you decide to work on another thing + before your first change is merged in. - In general, after pushing your topic branch up to your - repository on GitHub, you'll switch to the branch in the - GitHub UI and click "Pull Request" towards the top of the page - in order to open a pull request. + GitHub has some pretty good + [general documentation](http://help.github.com/) on using + their site. They also have documentation on + [creating pull requests](http://help.github.com/send-pull-requests/). - You'll want to make sure that you have the appropriate - destination branch in the repository under the puppetlabs - organization. This should be the same branch that you based - your changes off of. + In general, after pushing your topic branch up to your + repository on GitHub, you'll switch to the branch in the + GitHub UI and click "Pull Request" towards the top of the page + in order to open a pull request. - * Other pull requests + You'll want to make sure that you have the appropriate + destination branch in the repository under the puppetlabs + organization. This should be the same branch that you based + your changes off of. - If you already have a publicly accessible version of the - repository hosted elsewhere, and don't wish to or cannot use - GitHub, you can submit your change by requesting that we pull - the changes from your repository by sending an email to the - puppet-dev Google Groups mailing list. + 5. Update the related Redmine ticket. - `git-request-pull(1)` provides a handy way to generate the text - for the email requesting that we pull your changes (and does - some helpful sanity checks in the process). - - * Mailing patches to the mailing list - - If neither of the previous methods works for you, then you can - also mail the patches inline to the puppet-dev Google Group - using either `rake mail_patches`, or by using - `git-format-patch(1)`, and `git-send-email(1)` directly. - - `rake mail_patches` handles setting the appropriate flags to - `git-format-patch(1)` and `git-send-email(1)` for you, but - doesn't allow adding any commentary between the '---', and the - diffstat in the resulting email. It also requires that you - have created your topic branch in the form - `//`. - - If you decide to use `git-format-patch(1)` and - `git-send-email(1)` directly, please be sure to use the - following flags for `git-format-patch(1)`: -C -M -s -n - --subject-prefix='PATCH/puppet' - - * Attaching patches to Redmine - - As a method of last resort you can also directly attach the - output of `git-format-patch(1)`, or `git-diff(1)` to a Redmine - ticket. - - If you are generating the diff outside of Git, please be sure - to generate a unified diff. - - 4. Update the related Redmine ticket. - - If there's a Redmine ticket associated with the change you - submitted, then you should update the ticket to include the - location of your branch, and change the status to "In Topic - Branch Pending Merge", along with any other commentary you may - wish to make. + You should update the Redmine ticket associated + with the change you submitted to include the location of your branch + on the `branch` field of the ticket, and change the status to + "In Topic Branch Pending Review", along with any other commentary + you may wish to make. How to track the status of your change after it's been submitted ================================================================ -Shortly after opening a pull request on GitHub, there should be an -automatic message sent to the puppet-dev Google Groups mailing list -notifying people of this. This notification is used to let the Puppet +Shortly after opening a pull request, there should be an automatic +email sent via GitHub. This notification is used to let the Puppet development community know about your requested change to give them a chance to review, test, and comment on the change(s). -If you submitted your change via manually sending a pull request or -mailing the patches, then we keep track of these using -[patchwork](https://patchwork.puppetlabs.com). When code is merged -into the project it is automatically removed from patchwork, and the -Redmine ticket is manually updated with the commit SHA1. In addition, -the ticket status must be updated by the person who merges the topic -branch to a status of "Merged - Pending Release" - -We do our best to comment on or merge submitted changes within a week. +We do our best to comment on or merge submitted changes within a about week. However, if there hasn't been any commentary on the pull request or mailed patches, and it hasn't been merged in after a week, then feel free to ask for an update by replying on the mailing list to the automatic notification or mailed patches. It probably wasn't intentional, and probably just slipped through the cracks. Additional Resources ==================== * [Getting additional help](http://projects.puppetlabs.com/projects/puppet/wiki/Getting_Help) * [Writing tests](http://projects.puppetlabs.com/projects/puppet/wiki/Development_Writing_Tests) * [Bug tracker (Redmine)](http://projects.puppetlabs.com) -* [Patchwork](https://patchwork.puppetlabs.com) - * [Contributor License Agreement](https://projects.puppetlabs.com/contributor_licenses/sign) * [General GitHub documentation](http://help.github.com/) * [GitHub pull request documentation](http://help.github.com/send-pull-requests/) If you have commit access to the repository =========================================== Even if you have commit access to the repository, you'll still need to go through the process above, and have someone else review and merge in your changes. The rule is that all changes must be reviewed by a developer on the project (that didn't write the code) to ensure that all changes go through a code review process. Having someone other than the author of the topic branch recorded as performing the merge is the record that they performed the code review. * Merging topic branches When merging code from a topic branch into the integration branch (Ex: master, 2.7.x, 1.6.x, etc.), there should always be a merge commit. You can accomplish this by always providing the `--no-ff` flag to `git merge`. git merge --no-ff --log tickets/master/1234-fix-something-broken The reason for always forcing this merge commit is that it provides a consistent way to look up what changes & commits were in a topic branch, whether that topic branch had one, or 500 commits. For example, if the merge commit had an abbreviated SHA-1 of `coffeebad`, then you could use the following `git log` invocation to show you which commits it brought in: git log coffeebad^1..coffeebad^2 The following would show you which changes were made on the topic branch: git diff coffeebad^1...coffeebad^2 Because we _always_ merge the topic branch into the integration branch the first parent (`^1`) of a merge commit will be the most recent commit on the integration branch from just before we merged in the topic, and the second parent (`^2`) will always be the most recent commit that was made in the topic branch. This also serves as the record of who performed the code review, as mentioned above. diff --git a/README.md b/README.md index 2d67c0188..22c005cc7 100644 --- a/README.md +++ b/README.md @@ -1,43 +1,51 @@ Puppet ====== Puppet, an automated administrative engine for your Linux and Unix systems, performs administrative tasks (such as adding users, installing packages, and updating server configurations) based on a centralized specification. Documentation (and detailed installation instructions) can be found online at the [Puppet Docs site](http://docs.puppetlabs.com). + Installation ------------ Generally, you need the following things installed: * A supported Ruby version. Ruby 1.8.5, 1.8.7, and 1.9.2 are fully supported (with a handful of known issues under 1.9.2); Ruby 1.8.1 is supported on a best-effort basis for agent use only. Other versions of Ruby are used at your own risk, and Ruby 1.8.6, 1.9.0, and 1.9.1 are not recommended for compatibility reasons. * The Ruby OpenSSL library. For some reason, this often isn't included in the main ruby distributions. You can test for it by running 'ruby -ropenssl -e "puts :yep"'. If that errors out, you're missing the library. If your distribution doesn't come with the necessary library (e.g., on Debian and Ubuntu you need to install libopenssl-ruby), then you'll probably have to compile Ruby yourself, since it's part of the standard library and not available separately. You could probably just compile and install that one library, though. * Facter => 1.5.1 (available via your package manager or from the [Facter site](http://puppetlabs.com/projects/facter). +Contributions +------ +Please see our [Contibution +Documents](https://github.com/puppetlabs/puppet/blob/master/CONTRIBUTING.md) +and our [Developer +Documentation](https://github.com/puppetlabs/puppet/blob/master/README_DEVELOPER.md). + License ------- See LICENSE file. Support ------- Please log tickets and issues at our [Projects site](http://projects.puppetlabs.com) diff --git a/Rakefile b/Rakefile index e59d20ade..f63309150 100644 --- a/Rakefile +++ b/Rakefile @@ -1,53 +1,69 @@ # Rakefile for Puppet -*- ruby -*- $LOAD_PATH << File.join(File.dirname(__FILE__), 'tasks') +begin + require 'rubygems' + require 'rubygems/package_task' +rescue LoadError + # Users of older versions of Rake (0.8.7 for example) will not necessarily + # have rubygems installed, or the newer rubygems package_task for that + # matter. + require 'rake/packagetask' + require 'rake/gempackagetask' +end + require 'rake' -require 'rake/packagetask' -require 'rake/gempackagetask' require 'rspec' require "rspec/core/rake_task" + module Puppet - PUPPETVERSION = File.read('lib/puppet.rb')[/PUPPETVERSION *= *'(.*)'/,1] or fail "Couldn't find PUPPETVERSION" + %x{which git &> /dev/null} + if $?.success? and File.exist?('.git') + # remove the git hash from git describe string + PUPPETVERSION=%x{git describe}.chomp.gsub('-','.').split('.')[0..3].join('.') + else + PUPPETVERSION=File.read('lib/puppet.rb')[/PUPPETVERSION *= *'(.*)'/,1] or fail "Couldn't find PUPPETVERSION" + end end Dir['tasks/**/*.rake'].each { |t| load t } FILES = FileList[ '[A-Z]*', 'install.rb', 'bin/**/*', 'sbin/**/*', 'lib/**/*', 'conf/**/*', 'man/**/*', 'examples/**/*', 'ext/**/*', 'tasks/**/*', 'test/**/*', 'spec/**/*' ] Rake::PackageTask.new("puppet", Puppet::PUPPETVERSION) do |pkg| pkg.package_dir = 'pkg' pkg.need_tar_gz = true pkg.package_files = FILES.to_a end task :default do sh %{rake -T} end desc "Create the tarball and the gem - use when releasing" task :puppetpackages => [:create_gem, :package] RSpec::Core::RakeTask.new do |t| t.pattern ='spec/{unit,integration}/**/*.rb' t.fail_on_error = true end desc "Run the unit tests" task :unit do Dir.chdir("test") { sh "rake" } end diff --git a/lib/puppet/indirector/rest.rb b/lib/puppet/indirector/rest.rb index d76a2890f..717937453 100644 --- a/lib/puppet/indirector/rest.rb +++ b/lib/puppet/indirector/rest.rb @@ -1,160 +1,166 @@ require 'net/http' require 'uri' require 'puppet/network/http_pool' require 'puppet/network/http/api/v1' require 'puppet/network/http/compression' # Access objects via REST class Puppet::Indirector::REST < Puppet::Indirector::Terminus include Puppet::Network::HTTP::API::V1 include Puppet::Network::HTTP::Compression.module class << self attr_reader :server_setting, :port_setting end # Specify the setting that we should use to get the server name. def self.use_server_setting(setting) @server_setting = setting end def self.server Puppet.settings[server_setting || :server] end # Specify the setting that we should use to get the port. def self.use_port_setting(setting) @port_setting = setting end def self.port Puppet.settings[port_setting || :masterport].to_i end # Figure out the content type, turn that into a format, and use the format # to extract the body of the response. def deserialize(response, multiple = false) case response.code when "404" return nil when /^2/ raise "No content type in http response; cannot parse" unless response['content-type'] content_type = response['content-type'].gsub(/\s*;.*$/,'') # strip any appended charset body = uncompress_body(response) # Convert the response to a deserialized object. if multiple model.convert_from_multiple(content_type, body) else model.convert_from(content_type, body) end else # Raise the http error if we didn't get a 'success' of some kind. raise convert_to_http_error(response) end end def convert_to_http_error(response) message = "Error #{response.code} on SERVER: #{(response.body||'').empty? ? response.message : uncompress_body(response)}" Net::HTTPError.new(message, response) end # Provide appropriate headers. def headers add_accept_encoding({"Accept" => model.supported_formats.join(", ")}) end def network(request) Puppet::Network::HttpPool.http_instance(request.server || self.class.server, request.port || self.class.port) end [:get, :post, :head, :delete, :put].each do |method| define_method "http_#{method}" do |request, *args| http_request(method, request, *args) end end def http_request(method, request, *args) http_connection = network(request) peer_certs = [] + verify_errors = [] - # We add the callback to collect the certificates for use in constructing - # the error message if the verification failed. This is necessary since we - # don't have direct access to the cert that we expected the connection to - # use otherwise. - # http_connection.verify_callback = proc do |preverify_ok, ssl_context| + # We use the callback to collect the certificates for use in constructing + # the error message if the verification failed. This is necessary since we + # don't have direct access to the cert that we expected the connection to + # use otherwise. peer_certs << Puppet::SSL::Certificate.from_s(ssl_context.current_cert.to_pem) + # And also keep the detailed verification error if such an error occurs + if ssl_context.error_string and not preverify_ok + verify_errors << "#{ssl_context.error_string} for #{ssl_context.current_cert.subject}" + end preverify_ok end http_connection.send(method, *args) rescue OpenSSL::SSL::SSLError => error if error.message.include? "certificate verify failed" - raise Puppet::Error, "#{error.message}. This is often because the time is out of sync on the server or client" + msg = error.message + msg << ": [" + verify_errors.join('; ') + "]" + raise Puppet::Error, msg elsif error.message =~ /hostname (was )?not match/ raise unless cert = peer_certs.find { |c| c.name !~ /^puppet ca/i } valid_certnames = [cert.name, *cert.subject_alt_names].uniq msg = valid_certnames.length > 1 ? "one of #{valid_certnames.join(', ')}" : valid_certnames.first raise Puppet::Error, "Server hostname '#{http_connection.address}' did not match server certificate; expected #{msg}" else raise end end def find(request) uri, body = request_to_uri_and_body(request) uri_with_query_string = "#{uri}?#{body}" # WEBrick in Ruby 1.9.1 only supports up to 1024 character lines in an HTTP request # http://redmine.ruby-lang.org/issues/show/3991 response = if "GET #{uri_with_query_string} HTTP/1.1\r\n".length > 1024 http_post(request, uri, body, headers) else http_get(request, uri_with_query_string, headers) end result = deserialize response result.name = request.key if result.respond_to?(:name=) result end def head(request) response = http_head(request, indirection2uri(request), headers) case response.code when "404" return false when /^2/ return true else # Raise the http error if we didn't get a 'success' of some kind. raise convert_to_http_error(response) end end def search(request) unless result = deserialize(http_get(request, indirection2uri(request), headers), true) return [] end result end def destroy(request) raise ArgumentError, "DELETE does not accept options" unless request.options.empty? deserialize http_delete(request, indirection2uri(request), headers) end def save(request) raise ArgumentError, "PUT does not accept options" unless request.options.empty? deserialize http_put(request, indirection2uri(request), request.instance.render, headers.merge({ "Content-Type" => request.instance.mime })) end private def environment Puppet::Node::Environment.new end end diff --git a/lib/puppet/parser/ast/resource.rb b/lib/puppet/parser/ast/resource.rb index ce3c499c5..b0b4d2652 100644 --- a/lib/puppet/parser/ast/resource.rb +++ b/lib/puppet/parser/ast/resource.rb @@ -1,67 +1,74 @@ require 'puppet/parser/ast/resource_reference' # Any normal puppet resource declaration. Can point to a definition or a # builtin type. class Puppet::Parser::AST class Resource < AST::Branch associates_doc attr_accessor :type, :instances, :exported, :virtual # Does not actually return an object; instead sets an object # in the current scope. def evaluate(scope) # We want virtual to be true if exported is true. We can't # just set :virtual => self.virtual in the initialization, # because sometimes the :virtual attribute is set *after* # :exported, in which case it clobbers :exported if :exported # is true. Argh, this was a very tough one to track down. virt = self.virtual || self.exported # First level of implicit iteration: build a resource for each # instance. This handles things like: # file { '/foo': owner => blah; '/bar': owner => blah } @instances.collect { |instance| # Evaluate all of the specified params. paramobjects = instance.parameters.collect { |param| param.safeevaluate(scope) } resource_titles = instance.title.safeevaluate(scope) # it's easier to always use an array, even for only one name - resource_titles = [resource_titles] unless resource_titles.is_a?(Array) + resource_titles = [resource_titles].flatten.collect do |title| + case title + when ::String, ::Numeric, ::Symbol, true, false + title.to_s + else + raise Puppet::ParseError, "Resource title must be a String, not #{title.class}" + end + end fully_qualified_type, resource_titles = scope.resolve_type_and_titles(type, resource_titles) # Second level of implicit iteration; build a resource for each # title. This handles things like: # file { ['/foo', '/bar']: owner => blah } - resource_titles.flatten.collect { |resource_title| + resource_titles.collect { |resource_title| exceptwrap :type => Puppet::ParseError do resource = Puppet::Parser::Resource.new( fully_qualified_type, resource_title, :parameters => paramobjects, :file => self.file, :line => self.line, :exported => self.exported, :virtual => virt, :source => scope.source, :scope => scope, :strict => true ) if resource.resource_type.is_a? Puppet::Resource::Type resource.resource_type.instantiate_resource(scope, resource) end scope.compiler.add_resource(scope, resource) scope.compiler.evaluate_classes([resource_title],scope,false) if fully_qualified_type == 'class' resource end } }.flatten.reject { |resource| resource.nil? } end end end diff --git a/lib/puppet/provider/service/gentoo.rb b/lib/puppet/provider/service/gentoo.rb index 4beb9ea6c..46e41e23c 100644 --- a/lib/puppet/provider/service/gentoo.rb +++ b/lib/puppet/provider/service/gentoo.rb @@ -1,56 +1,54 @@ # Manage gentoo services. Start/stop is the same as InitSvc, but enable/disable # is special. Puppet::Type.type(:service).provide :gentoo, :parent => :init do desc <<-EOT Gentoo's form of `init`-style service management. Uses `rc-update` for service enabling and disabling. EOT commands :update => "/sbin/rc-update" confine :operatingsystem => :gentoo - defaultfor :operatingsystem => :gentoo - def self.defpath superclass.defpath end def self.instances # this exclude list was found with grep -L '\/sbin\/runscript' /etc/init.d/* self.get_services(self.defpath, ['functions.sh', 'reboot.sh', 'shutdown.sh']) end def disable output = update :del, @resource[:name], :default rescue Puppet::ExecutionFailure raise Puppet::Error, "Could not disable #{self.name}: #{output}" end def enabled? begin output = update :show rescue Puppet::ExecutionFailure return :false end line = output.split(/\n/).find { |l| l.include?(@resource[:name]) } return :false unless line # If it's enabled then it will print output showing service | runlevel if output =~ /^\s*#{@resource[:name]}\s*\|\s*(boot|default)/ return :true else return :false end end def enable output = update :add, @resource[:name], :default rescue Puppet::ExecutionFailure raise Puppet::Error, "Could not enable #{self.name}: #{output}" end end diff --git a/lib/puppet/provider/service/openrc.rb b/lib/puppet/provider/service/openrc.rb new file mode 100644 index 000000000..a59f1d304 --- /dev/null +++ b/lib/puppet/provider/service/openrc.rb @@ -0,0 +1,69 @@ +# Gentoo OpenRC +Puppet::Type.type(:service).provide :openrc, :parent => :base do + desc <<-EOT + Support for Gentoo's OpenRC initskripts + + Uses rc-update, rc-status and rc-service to manage services. + + EOT + + defaultfor :operatingsystem => :gentoo + defaultfor :operatingsystem => :funtoo + + commands :rcservice => '/sbin/rc-service' + commands :rcstatus => '/bin/rc-status' + commands :rcupdate => '/sbin/rc-update' + + self::STATUSLINE = /^\s+(.*?)\s*\[\s*(.*)\s*\]$/ + + def enable + rcupdate('-C', :add, @resource[:name]) + end + + def disable + rcupdate('-C', :del, @resource[:name]) + end + + # rc-status -a shows all runlevels and dynamic runlevels which + # are not considered as enabled. We have to find out under which + # runlevel our service is listed + def enabled? + enabled = :false + rcstatus('-C', '-a').each_line do |line| + case line.chomp + when /^Runlevel: / + enabled = :true + when /^\S+/ # caption of a dynamic runlevel + enabled = :false + when self.class::STATUSLINE + return enabled if @resource[:name] == $1 + end + end + :false + end + + def self.instances + instances = [] + rcservice('-C', '--list').each_line do |line| + instances << new(:name => line.chomp) + end + instances + end + + def restartcmd + (@resource[:hasrestart] == :true) && [command(:rcservice), @resource[:name], :restart] + end + + def startcmd + [command(:rcservice), @resource[:name], :start ] + end + + def stopcmd + [command(:rcservice), @resource[:name], :stop] + end + + def statuscmd + ((@resource.provider.get(:hasstatus) == true) || (@resource[:hasstatus] == :true)) && [command(:rcservice), @resource[:name], :status] + end + +end diff --git a/lib/puppet/provider/user/aix.rb b/lib/puppet/provider/user/aix.rb index ce0b8a3b8..6c9679d8e 100755 --- a/lib/puppet/provider/user/aix.rb +++ b/lib/puppet/provider/user/aix.rb @@ -1,353 +1,357 @@ # # User Puppet provider for AIX. It uses standard commands to manage users: # mkuser, rmuser, lsuser, chuser # # Notes: # - AIX users can have expiry date defined with minute granularity, # but puppet does not allow it. There is a ticket open for that (#5431) # - AIX maximum password age is in WEEKs, not days # # See http://projects.puppetlabs.com/projects/puppet/wiki/Development_Provider_Development # for more information # # Author:: Hector Rivas Gandara # require 'puppet/provider/aixobject' require 'tempfile' require 'date' Puppet::Type.type(:user).provide :aix, :parent => Puppet::Provider::AixObject do desc "User management for AIX." # This will the the default provider for this platform defaultfor :operatingsystem => :aix confine :operatingsystem => :aix # Commands that manage the element commands :list => "/usr/sbin/lsuser" commands :add => "/usr/bin/mkuser" commands :delete => "/usr/sbin/rmuser" commands :modify => "/usr/bin/chuser" commands :lsgroup => "/usr/sbin/lsgroup" commands :chpasswd => "/bin/chpasswd" # Provider features has_features :manages_aix_lam has_features :manages_homedir, :manages_passwords has_features :manages_expiry, :manages_password_age # Attribute verification (TODO) #verify :gid, "GID must be an string or int of a valid group" do |value| # value.is_a? String || value.is_a? Integer #end # #verify :groups, "Groups must be comma-separated" do |value| # value !~ /\s/ #end # User attributes to ignore from AIX output. def self.attribute_ignore [] end # AIX attributes to properties mapping. # # Valid attributes to be managed by this provider. # It is a list with of hash # :aix_attr AIX command attribute name # :puppet_prop Puppet propertie name # :to Method to adapt puppet property to aix command value. Optional. # :from Method to adapt aix command value to puppet property. Optional self.attribute_mapping = [ #:name => :name, {:aix_attr => :pgrp, :puppet_prop => :gid, :to => :gid_to_attr, :from => :gid_from_attr}, {:aix_attr => :id, :puppet_prop => :uid}, {:aix_attr => :groups, :puppet_prop => :groups}, {:aix_attr => :home, :puppet_prop => :home}, {:aix_attr => :shell, :puppet_prop => :shell}, {:aix_attr => :expires, :puppet_prop => :expiry, :to => :expiry_to_attr, :from => :expiry_from_attr}, {:aix_attr => :maxage, :puppet_prop => :password_max_age}, {:aix_attr => :minage, :puppet_prop => :password_min_age}, {:aix_attr => :attributes, :puppet_prop => :attributes}, ] #-------------- # Command definition # Return the IA module arguments based on the resource param ia_load_module def get_ia_module_args if @resource[:ia_load_module] ["-R", @resource[:ia_load_module].to_s] else [] end end # List groups and Ids def lsgroupscmd(value=@resource[:name]) [command(:lsgroup)] + self.get_ia_module_args + ["-a", "id", value] end def lscmd(value=@resource[:name]) [self.class.command(:list)] + self.get_ia_module_args + [ value] end def lsallcmd() lscmd("ALL") end def addcmd(extra_attrs = []) # Here we use the @resource.to_hash to get the list of provided parameters # Puppet does not call to self.= method if it does not exists. # # It gets an extra list of arguments to add to the user. [self.class.command(:add)] + self.get_ia_module_args + self.hash2args(@resource.to_hash) + extra_attrs + [@resource[:name]] end # Get modify command. Set translate=false if no mapping must be used. # Needed for special properties like "attributes" def modifycmd(hash = property_hash) args = self.hash2args(hash) return nil if args.empty? [self.class.command(:modify)] + self.get_ia_module_args + args + [@resource[:name]] end def deletecmd [self.class.command(:delete)] + self.get_ia_module_args + [@resource[:name]] end #-------------- # We overwrite the create function to change the password after creation. def create super # Reset the password if needed self.password = @resource[:password] if @resource[:password] end def get_arguments(key, value, mapping, objectinfo) # In the case of attributes, return a list of key=vlaue if key == :attributes raise Puppet::Error, "Attributes must be a list of pairs key=value on #{@resource.class.name}[#{@resource.name}]" \ unless value and value.is_a? Hash return value.select { |k,v| true }.map { |pair| pair.join("=") } end super(key, value, mapping, objectinfo) end # Get the groupname from its id def self.groupname_by_id(gid) groupname=nil execute(lsgroupscmd("ALL")).each_line { |entry| attrs = self.parse_attr_list(entry, nil) if attrs and attrs.include? :id and gid == attrs[:id].to_i groupname = entry.split(" ")[0] end } groupname end # Get the groupname from its id def groupid_by_name(groupname) attrs = self.parse_attr_list(execute(lsgroupscmd(groupname)).split("\n")[0], nil) attrs ? attrs[:id].to_i : nil end # Check that a group exists and is valid def verify_group(value) if value.is_a? Integer or value.is_a? Fixnum groupname = self.groupname_by_id(value) raise ArgumentError, "AIX group must be a valid existing group" unless groupname else raise ArgumentError, "AIX group must be a valid existing group" unless groupid_by_name(value) groupname = value end groupname end # The user's primary group. Can be specified numerically or by name. def gid_to_attr(value) verify_group(value) end # Get the group gid from its name def gid_from_attr(value) groupid_by_name(value) end # The expiry date for this user. Must be provided in # a zero padded YYYY-MM-DD HH:MM format def expiry_to_attr(value) # For chuser the expires parameter is a 10-character string in the MMDDhhmmyy format # that is,"%m%d%H%M%y" newdate = '0' if value.is_a? String and value!="0000-00-00" d = DateTime.parse(value, "%Y-%m-%d %H:%M") newdate = d.strftime("%m%d%H%M%y") end newdate end def expiry_from_attr(value) if value =~ /(..)(..)(..)(..)(..)/ #d= DateTime.parse("20#{$5}-#{$1}-#{$2} #{$3}:#{$4}") #expiry_date = d.strftime("%Y-%m-%d %H:%M") #expiry_date = d.strftime("%Y-%m-%d") expiry_date = "20#{$5}-#{$1}-#{$2}" else Puppet.warn("Could not convert AIX expires date '#{value}' on #{@resource.class.name}[#{@resource.name}]") \ unless value == '0' expiry_date = :absent end expiry_date end #-------------------------------- # Getter and Setter # When the provider is initialized, create getter/setter methods for each # property our resource type supports. # If setter or getter already defined it will not be overwritten #- **password** # The user's password, in whatever encrypted format the local machine # requires. Be sure to enclose any value that includes a dollar sign ($) # in single quotes ('). Requires features manages_passwords. # # Retrieve the password parsing directly the /etc/security/passwd def password password = :absent user = @resource[:name] f = File.open("/etc/security/passwd", 'r') # Skip to the user f.each_line { |l| break if l =~ /^#{user}:\s*$/ } if ! f.eof? f.each_line { |l| # If there is a new user stanza, stop break if l =~ /^\S*:\s*$/ # If the password= entry is found, return it if l =~ /^\s*password\s*=\s*(.*)$/ password = $1; break; end } end f.close() return password end def password=(value) user = @resource[:name] # Puppet execute does not support strings as input, only files. tmpfile = Tempfile.new('puppet_#{user}_pw') tmpfile << "#{user}:#{value}\n" tmpfile.close() # Options '-e', '-c', use encrypted password and clear flags - # Must receibe "user:enc_password" as input + # Must receive "user:enc_password" as input # command, arguments = {:failonfail => true, :combine => true} - cmd = [self.class.command(:chpasswd),"-R", self.class.ia_module, - '-e', '-c', user] + # Fix for bugs #11200 and #10915 + cmd = [self.class.command(:chpasswd), get_ia_module_args, '-e', '-c', user].flatten begin - execute(cmd, {:failonfail => true, :combine => true, :stdinfile => tmpfile.path }) + output = execute(cmd, {:failonfail => false, :combine => true, :stdinfile => tmpfile.path }) + # chpasswd can return 1, even on success (at least on AIX 6.1); empty output indicates success + if output != "" + raise Puppet::ExecutionFailure, "chpasswd said #{output}" + end rescue Puppet::ExecutionFailure => detail raise Puppet::Error, "Could not set #{param} on #{@resource.class.name}[#{@resource.name}]: #{detail}" ensure tmpfile.delete() end end def filter_attributes(hash) # Return only not managed attributtes. hash.select { |k,v| !self.class.attribute_mapping_from.include?(k) and !self.class.attribute_ignore.include?(k) }.inject({}) { |hash, array| hash[array[0]] = array[1]; hash } end def attributes filter_attributes(getosinfo(refresh = false)) end def attributes=(attr_hash) #self.class.validate(param, value) param = :attributes cmd = modifycmd({param => filter_attributes(attr_hash)}) if cmd begin execute(cmd) rescue Puppet::ExecutionFailure => detail raise Puppet::Error, "Could not set #{param} on #{@resource.class.name}[#{@resource.name}]: #{detail}" end end end #- **comment** # A description of the user. Generally is a user's full name. #def comment=(value) #end # #def comment #end # UNSUPPORTED #- **profile_membership** # Whether specified roles should be treated as the only roles # of which the user is a member or whether they should merely # be treated as the minimum membership list. Valid values are # `inclusive`, `minimum`. # UNSUPPORTED #- **profiles** # The profiles the user has. Multiple profiles should be # specified as an array. Requires features manages_solaris_rbac. # UNSUPPORTED #- **project** # The name of the project associated with a user Requires features # manages_solaris_rbac. # UNSUPPORTED #- **role_membership** # Whether specified roles should be treated as the only roles # of which the user is a member or whether they should merely # be treated as the minimum membership list. Valid values are # `inclusive`, `minimum`. # UNSUPPORTED #- **roles** # The roles the user has. Multiple roles should be # specified as an array. Requires features manages_solaris_rbac. # UNSUPPORTED #- **key_membership** # Whether specified key value pairs should be treated as the only # attributes # of the user or whether they should merely # be treated as the minimum list. Valid values are `inclusive`, # `minimum`. # UNSUPPORTED #- **keys** # Specify user attributes in an array of keyvalue pairs Requires features # manages_solaris_rbac. # UNSUPPORTED #- **allowdupe** # Whether to allow duplicate UIDs. Valid values are `true`, `false`. # UNSUPPORTED #- **auths** # The auths the user has. Multiple auths should be # specified as an array. Requires features manages_solaris_rbac. # UNSUPPORTED #- **auth_membership** # Whether specified auths should be treated as the only auths # of which the user is a member or whether they should merely # be treated as the minimum membership list. Valid values are # `inclusive`, `minimum`. # UNSUPPORTED end diff --git a/lib/puppet/provider/user/useradd.rb b/lib/puppet/provider/user/useradd.rb index e3749057f..c44c19a3a 100644 --- a/lib/puppet/provider/user/useradd.rb +++ b/lib/puppet/provider/user/useradd.rb @@ -1,116 +1,122 @@ require 'puppet/provider/nameservice/objectadd' Puppet::Type.type(:user).provide :useradd, :parent => Puppet::Provider::NameService::ObjectAdd do desc "User management via `useradd` and its ilk. Note that you will need to install Ruby's shadow password library (often known as `ruby-libshadow`) if you wish to manage user passwords." commands :add => "useradd", :delete => "userdel", :modify => "usermod", :password => "chage" options :home, :flag => "-d", :method => :dir options :comment, :method => :gecos options :groups, :flag => "-G" options :password_min_age, :flag => "-m" options :password_max_age, :flag => "-M" verify :gid, "GID must be an integer" do |value| value.is_a? Integer end verify :groups, "Groups must be comma-separated" do |value| value !~ /\s/ end has_features :manages_homedir, :allows_duplicates, :manages_expiry, :system_users has_features :manages_passwords, :manages_password_age if Puppet.features.libshadow? def check_allow_dup @resource.allowdupe? ? ["-o"] : [] end def check_manage_home cmd = [] if @resource.managehome? cmd << "-m" elsif %w{Fedora RedHat CentOS OEL OVS}.include?(Facter.value("operatingsystem")) cmd << "-M" end cmd end def check_manage_expiry cmd = [] if @resource[:expiry] cmd << "-e #{@resource[:expiry]}" end cmd end def check_system_users @resource.system? ? ["-r"] : [] end def add_properties cmd = [] Puppet::Type.type(:user).validproperties.each do |property| next if property == :ensure next if property.to_s =~ /password_.+_age/ # the value needs to be quoted, mostly because -c might # have spaces in it if value = @resource.should(property) and value != "" cmd << flag(property) << value end end cmd end def addcmd cmd = [command(:add)] cmd += add_properties cmd += check_allow_dup cmd += check_manage_home cmd += check_manage_expiry cmd += check_system_users cmd << @resource[:name] end + + def deletecmd + cmd = [command(:delete)] + cmd += @resource.managehome? ? ['-r'] : [] + cmd << @resource[:name] + end def passcmd age_limits = [:password_min_age, :password_max_age].select { |property| @resource.should(property) } if age_limits.empty? nil else [command(:password),age_limits.collect { |property| [flag(property), @resource.should(property)]}, @resource[:name]].flatten end end def password_min_age if Puppet.features.libshadow? if ent = Shadow::Passwd.getspnam(@resource.name) return ent.sp_min end end :absent end def password_max_age if Puppet.features.libshadow? if ent = Shadow::Passwd.getspnam(@resource.name) return ent.sp_max end end :absent end # Retrieve the password using the Shadow Password library def password if Puppet.features.libshadow? if ent = Shadow::Passwd.getspnam(@resource.name) return ent.sp_pwdp end end :absent end end diff --git a/lib/puppet/ssl/certificate_authority/interface.rb b/lib/puppet/ssl/certificate_authority/interface.rb index 775b7b0c2..6d4114c41 100644 --- a/lib/puppet/ssl/certificate_authority/interface.rb +++ b/lib/puppet/ssl/certificate_authority/interface.rb @@ -1,181 +1,182 @@ # This class is basically a hidden class that knows how to act # on the CA. It's only used by the 'puppetca' executable, and its # job is to provide a CLI-like interface to the CA class. module Puppet module SSL class CertificateAuthority class Interface INTERFACE_METHODS = [:destroy, :list, :revoke, :generate, :sign, :print, :verify, :fingerprint] class InterfaceError < ArgumentError; end attr_reader :method, :subjects, :digest, :options # Actually perform the work. def apply(ca) unless subjects or method == :list raise ArgumentError, "You must provide hosts or :all when using #{method}" end begin return send(method, ca) if respond_to?(method) (subjects == :all ? ca.list : subjects).each do |host| ca.send(method, host) end rescue InterfaceError raise rescue => detail puts detail.backtrace if Puppet[:trace] Puppet.err "Could not call #{method}: #{detail}" + raise end end def generate(ca) raise InterfaceError, "It makes no sense to generate all hosts; you must specify a list" if subjects == :all subjects.each do |host| ca.generate(host, options) end end def initialize(method, options) self.method = method self.subjects = options.delete(:to) @digest = options.delete(:digest) || :MD5 @options = options end # List the hosts. def list(ca) signed = ca.list requests = ca.waiting? case subjects when :all hosts = [signed, requests].flatten when :signed hosts = signed.flatten when nil hosts = requests else hosts = subjects end certs = {:signed => {}, :invalid => {}, :request => {}} return if hosts.empty? hosts.uniq.sort.each do |host| begin ca.verify(host) unless requests.include?(host) rescue Puppet::SSL::CertificateAuthority::CertificateVerificationError => details verify_error = details.to_s end if verify_error cert = Puppet::SSL::Certificate.indirection.find(host) certs[:invalid][host] = [cert, verify_error] elsif signed.include?(host) cert = Puppet::SSL::Certificate.indirection.find(host) certs[:signed][host] = cert else req = Puppet::SSL::CertificateRequest.indirection.find(host) certs[:request][host] = req end end names = certs.values.map(&:keys).flatten name_width = names.sort_by(&:length).last.length rescue 0 # We quote these names, so account for those characters name_width += 2 output = [:request, :signed, :invalid].map do |type| next if certs[type].empty? certs[type].map do |host,info| format_host(ca, host, type, info, name_width) end end.flatten.compact.sort.join("\n") puts output end def format_host(ca, host, type, info, width) certish, verify_error = info alt_names = case type when :signed certish.subject_alt_names when :request certish.subject_alt_names else [] end alt_names.delete(host) alt_str = "(alt names: #{alt_names.map(&:inspect).join(', ')})" unless alt_names.empty? glyph = {:signed => '+', :request => ' ', :invalid => '-'}[type] name = host.inspect.ljust(width) fingerprint = "(#{ca.fingerprint(host, @digest)})" explanation = "(#{verify_error})" if verify_error [glyph, name, fingerprint, alt_str, explanation].compact.join(' ') end # Set the method to apply. def method=(method) raise ArgumentError, "Invalid method #{method} to apply" unless INTERFACE_METHODS.include?(method) @method = method end # Print certificate information. def print(ca) (subjects == :all ? ca.list : subjects).each do |host| if value = ca.print(host) puts value else Puppet.err "Could not find certificate for #{host}" end end end # Print certificate information. def fingerprint(ca) (subjects == :all ? ca.list + ca.waiting?: subjects).each do |host| if value = ca.fingerprint(host, @digest) puts "#{host} #{value}" else Puppet.err "Could not find certificate for #{host}" end end end # Sign a given certificate. def sign(ca) list = subjects == :all ? ca.waiting? : subjects raise InterfaceError, "No waiting certificate requests to sign" if list.empty? list.each do |host| ca.sign(host, options[:allow_dns_alt_names]) end end # Set the list of hosts we're operating on. Also supports keywords. def subjects=(value) unless value == :all or value == :signed or value.is_a?(Array) raise ArgumentError, "Subjects must be an array or :all; not #{value}" end value = nil if value.is_a?(Array) and value.empty? @subjects = value end end end end end diff --git a/lib/puppet/type/file.rb b/lib/puppet/type/file.rb index eeb0d62e6..dd13c6872 100644 --- a/lib/puppet/type/file.rb +++ b/lib/puppet/type/file.rb @@ -1,837 +1,839 @@ require 'digest/md5' require 'cgi' require 'etc' require 'uri' require 'fileutils' require 'enumerator' require 'pathname' require 'puppet/network/handler' require 'puppet/util/diff' require 'puppet/util/checksums' require 'puppet/util/backups' require 'puppet/util/symbolic_file_mode' Puppet::Type.newtype(:file) do include Puppet::Util::MethodHelper include Puppet::Util::Checksums include Puppet::Util::Backups include Puppet::Util::SymbolicFileMode @doc = "Manages files, including their content, ownership, and permissions. The `file` type can manage normal files, directories, and symlinks; the type should be specified in the `ensure` attribute. Note that symlinks cannot be managed on Windows systems. File contents can be managed directly with the `content` attribute, or downloaded from a remote source using the `source` attribute; the latter can also be used to recursively serve directories (when the `recurse` attribute is set to `true` or `local`). On Windows, note that file contents are managed in binary mode; Puppet never automatically translates line endings. **Autorequires:** If Puppet is managing the user or group that owns a file, the file resource will autorequire them. If Puppet is managing any parent directories of a file, the file resource will autorequire them." def self.title_patterns [ [ /^(.*?)\/*\Z/m, [ [ :path, lambda{|x| x} ] ] ] ] end newparam(:path) do desc <<-EOT The path to the file to manage. Must be fully qualified. On Windows, the path should include the drive letter and should use `/` as the separator character (rather than `\\`). EOT isnamevar validate do |value| unless Puppet::Util.absolute_path?(value) fail Puppet::Error, "File paths must be fully qualified, not '#{value}'" end end # convert the current path in an index into the collection and the last # path name. The aim is to use less storage for all common paths in a hierarchy munge do |value| # We know the value is absolute, so expanding it will just standardize it. path, name = ::File.split(::File.expand_path(value)) { :index => Puppet::FileCollection.collection.index(path), :name => name } end # and the reverse unmunge do |value| basedir = Puppet::FileCollection.collection.path(value[:index]) ::File.join( basedir, value[:name] ) end end newparam(:backup) do desc "Whether files should be backed up before being replaced. The preferred method of backing files up is via a `filebucket`, which stores files by their MD5 sums and allows easy retrieval without littering directories with backups. You can specify a local filebucket or a network-accessible server-based filebucket by setting `backup => bucket-name`. Alternatively, if you specify any value that begins with a `.` (e.g., `.puppet-bak`), then Puppet will use copy the file in the same directory with that value as the extension of the backup. Setting `backup => false` disables all backups of the file in question. Puppet automatically creates a local filebucket named `puppet` and defaults to backing up there. To use a server-based filebucket, you must specify one in your configuration. filebucket { main: server => puppet, path => false, # The path => false line works around a known issue with the filebucket type. } The `puppet master` daemon creates a filebucket by default, so you can usually back up to your main server with this configuration. Once you've described the bucket in your configuration, you can use it in any file's backup attribute: file { \"/my/file\": source => \"/path/in/nfs/or/something\", backup => main } This will back the file up to the central server. At this point, the benefits of using a central filebucket are that you do not have backup files lying around on each of your machines, a given version of a file is only backed up once, you can restore any given file manually (no matter how old), and you can use Puppet Dashboard to view file contents. Eventually, transactional support will be able to automatically restore filebucketed files. " defaultto "puppet" munge do |value| # I don't really know how this is happening. value = value.shift if value.is_a?(Array) case value when false, "false", :false false when true, "true", ".puppet-bak", :true ".puppet-bak" when String value else self.fail "Invalid backup type #{value.inspect}" end end end newparam(:recurse) do desc "Whether and how deeply to do recursive management. Options are: * `inf,true` --- Regular style recursion on both remote and local directory structure. * `remote` --- Descends recursively into the remote directory but not the local directory. Allows copying of a few files into a directory containing many unmanaged files without scanning all the local files. * `false` --- Default of no recursion. * `[0-9]+` --- Same as true, but limit recursion. Warning: this syntax has been deprecated in favor of the `recurselimit` attribute. " newvalues(:true, :false, :inf, :remote, /^[0-9]+$/) # Replace the validation so that we allow numbers in # addition to string representations of them. validate { |arg| } munge do |value| newval = super(value) case newval when :true, :inf; true when :false; false when :remote; :remote when Integer, Fixnum, Bignum self.warning "Setting recursion depth with the recurse parameter is now deprecated, please use recurselimit" # recurse == 0 means no recursion return false if value == 0 resource[:recurselimit] = value true when /^\d+$/ self.warning "Setting recursion depth with the recurse parameter is now deprecated, please use recurselimit" value = Integer(value) # recurse == 0 means no recursion return false if value == 0 resource[:recurselimit] = value true else self.fail "Invalid recurse value #{value.inspect}" end end end newparam(:recurselimit) do desc "How deeply to do recursive management." newvalues(/^[0-9]+$/) munge do |value| newval = super(value) case newval when Integer, Fixnum, Bignum; value when /^\d+$/; Integer(value) else self.fail "Invalid recurselimit value #{value.inspect}" end end end newparam(:replace, :boolean => true) do desc "Whether to replace a file that already exists on the local system but whose content doesn't match what the `source` or `content` attribute specifies. Setting this to false allows file resources to initialize files without overwriting future changes. Note that this only affects content; Puppet will still manage ownership and permissions. Defaults to `true`." newvalues(:true, :false) aliasvalue(:yes, :true) aliasvalue(:no, :false) defaultto :true end newparam(:force, :boolean => true) do desc "Perform the file operation even if it will destroy one or more directories. You must use `force` in order to: * `purge` subdirectories * Replace directories with files or links * Remove a directory when `ensure => absent`" newvalues(:true, :false) defaultto false end newparam(:ignore) do desc "A parameter which omits action on files matching specified patterns during recursion. Uses Ruby's builtin globbing engine, so shell metacharacters are fully supported, e.g. `[a-z]*`. Matches that would descend into the directory structure are ignored, e.g., `*/*`." validate do |value| unless value.is_a?(Array) or value.is_a?(String) or value == false self.devfail "Ignore must be a string or an Array" end end end newparam(:links) do desc "How to handle links during file actions. During file copying, `follow` will copy the target file instead of the link, `manage` will copy the link itself, and `ignore` will just pass it by. When not copying, `manage` and `ignore` behave equivalently (because you cannot really ignore links entirely during local recursion), and `follow` will manage the file to which the link points." newvalues(:follow, :manage) defaultto :manage end newparam(:purge, :boolean => true) do desc "Whether unmanaged files should be purged. This option only makes sense when managing directories with `recurse => true`. * When recursively duplicating an entire directory with the `source` attribute, `purge => true` will automatically purge any files that are not in the source directory. * When managing files in a directory as individual resources, setting `purge => true` will purge any files that aren't being specifically managed. If you have a filebucket configured, the purged files will be uploaded, but if you do not, this will destroy data." defaultto :false newvalues(:true, :false) end newparam(:sourceselect) do desc "Whether to copy all valid sources, or just the first one. This parameter only affects recursive directory copies; by default, the first valid source is the only one used, but if this parameter is set to `all`, then all valid sources will have all of their contents copied to the local system. If a given file exists in more than one source, the version from the earliest source in the list will be used." defaultto :first newvalues(:first, :all) end # Autorequire the nearest ancestor directory found in the catalog. autorequire(:file) do req = [] path = Pathname.new(self[:path]) if !path.root? # Start at our parent, to avoid autorequiring ourself parents = path.parent.enum_for(:ascend) if found = parents.find { |p| catalog.resource(:file, p.to_s) } req << found.to_s end end # if the resource is a link, make sure the target is created first req << self[:target] if self[:target] req end # Autorequire the owner and group of the file. {:user => :owner, :group => :group}.each do |type, property| autorequire(type) do if @parameters.include?(property) # The user/group property automatically converts to IDs next unless should = @parameters[property].shouldorig val = should[0] if val.is_a?(Integer) or val =~ /^\d+$/ nil else val end end end end CREATORS = [:content, :source, :target] SOURCE_ONLY_CHECKSUMS = [:none, :ctime, :mtime] validate do creator_count = 0 CREATORS.each do |param| creator_count += 1 if self.should(param) end creator_count += 1 if @parameters.include?(:source) self.fail "You cannot specify more than one of #{CREATORS.collect { |p| p.to_s}.join(", ")}" if creator_count > 1 self.fail "You cannot specify a remote recursion without a source" if !self[:source] and self[:recurse] == :remote self.fail "You cannot specify source when using checksum 'none'" if self[:checksum] == :none && !self[:source].nil? SOURCE_ONLY_CHECKSUMS.each do |checksum_type| self.fail "You cannot specify content when using checksum '#{checksum_type}'" if self[:checksum] == checksum_type && !self[:content].nil? end self.warning "Possible error: recurselimit is set but not recurse, no recursion will happen" if !self[:recurse] and self[:recurselimit] provider.validate if provider.respond_to?(:validate) end def self.[](path) return nil unless path super(path.gsub(/\/+/, '/').sub(/\/$/, '')) end def self.instances return [] end # Determine the user to write files as. def asuser if self.should(:owner) and ! self.should(:owner).is_a?(Symbol) writeable = Puppet::Util::SUIDManager.asuser(self.should(:owner)) { FileTest.writable?(::File.dirname(self[:path])) } # If the parent directory is writeable, then we execute # as the user in question. Otherwise we'll rely on # the 'owner' property to do things. asuser = self.should(:owner) if writeable end asuser end def bucket return @bucket if @bucket backup = self[:backup] return nil unless backup return nil if backup =~ /^\./ unless catalog or backup == "puppet" fail "Can not find filebucket for backups without a catalog" end unless catalog and filebucket = catalog.resource(:filebucket, backup) or backup == "puppet" fail "Could not find filebucket #{backup} specified in backup" end return default_bucket unless filebucket @bucket = filebucket.bucket @bucket end def default_bucket Puppet::Type.type(:filebucket).mkdefaultbucket.bucket end # Does the file currently exist? Just checks for whether # we have a stat def exist? stat ? true : false end # We have to do some extra finishing, to retrieve our bucket if # there is one. def finish # Look up our bucket, if there is one bucket super end # Create any children via recursion or whatever. def eval_generate return [] unless self.recurse? recurse #recurse.reject do |resource| # catalog.resource(:file, resource[:path]) #end.each do |child| # catalog.add_resource child # catalog.relationship_graph.add_edge self, child #end end def ancestors ancestors = Pathname.new(self[:path]).enum_for(:ascend).map(&:to_s) ancestors.delete(self[:path]) ancestors end def flush # We want to make sure we retrieve metadata anew on each transaction. @parameters.each do |name, param| param.flush if param.respond_to?(:flush) end @stat = :needs_stat end def initialize(hash) # Used for caching clients @clients = {} super # If they've specified a source, we get our 'should' values # from it. unless self[:ensure] if self[:target] - self[:ensure] = :symlink + self[:ensure] = :link elsif self[:content] self[:ensure] = :file end end @stat = :needs_stat end # Configure discovered resources to be purged. def mark_children_for_purging(children) children.each do |name, child| next if child[:source] child[:ensure] = :absent end end # Create a new file or directory object as a child to the current # object. def newchild(path) full_path = ::File.join(self[:path], path) # Add some new values to our original arguments -- these are the ones # set at initialization. We specifically want to exclude any param # values set by the :source property or any default values. # LAK:NOTE This is kind of silly, because the whole point here is that # the values set at initialization should live as long as the resource # but values set by default or by :source should only live for the transaction # or so. Unfortunately, we don't have a straightforward way to manage # the different lifetimes of this data, so we kludge it like this. # The right-side hash wins in the merge. options = @original_parameters.merge(:path => full_path).reject { |param, value| value.nil? } # These should never be passed to our children. [:parent, :ensure, :recurse, :recurselimit, :target, :alias, :source].each do |param| options.delete(param) if options.include?(param) end self.class.new(options) end # Files handle paths specially, because they just lengthen their # path names, rather than including the full parent's title each # time. def pathbuilder # We specifically need to call the method here, so it looks # up our parent in the catalog graph. if parent = parent() # We only need to behave specially when our parent is also # a file if parent.is_a?(self.class) # Remove the parent file name list = parent.pathbuilder list.pop # remove the parent's path info return list << self.ref else return super end else return [self.ref] end end # Should we be purging? def purge? @parameters.include?(:purge) and (self[:purge] == :true or self[:purge] == "true") end # Recursively generate a list of file resources, which will # be used to copy remote files, manage local files, and/or make links # to map to another directory. def recurse children = (self[:recurse] == :remote) ? {} : recurse_local if self[:target] recurse_link(children) elsif self[:source] recurse_remote(children) end # If we're purging resources, then delete any resource that isn't on the # remote system. mark_children_for_purging(children) if self.purge? result = children.values.sort { |a, b| a[:path] <=> b[:path] } remove_less_specific_files(result) end # This is to fix bug #2296, where two files recurse over the same # set of files. It's a rare case, and when it does happen you're # not likely to have many actual conflicts, which is good, because # this is a pretty inefficient implementation. def remove_less_specific_files(files) mypath = self[:path].split(::File::Separator) other_paths = catalog.vertices. select { |r| r.is_a?(self.class) and r[:path] != self[:path] }. collect { |r| r[:path].split(::File::Separator) }. select { |p| p[0,mypath.length] == mypath } return files if other_paths.empty? files.reject { |file| path = file[:path].split(::File::Separator) other_paths.any? { |p| path[0,p.length] == p } } end # A simple method for determining whether we should be recursing. def recurse? self[:recurse] == true or self[:recurse] == :remote end # Recurse the target of the link. def recurse_link(children) perform_recursion(self[:target]).each do |meta| if meta.relative_path == "." self[:ensure] = :directory next end children[meta.relative_path] ||= newchild(meta.relative_path) if meta.ftype == "directory" children[meta.relative_path][:ensure] = :directory else children[meta.relative_path][:ensure] = :link children[meta.relative_path][:target] = meta.full_path end end children end # Recurse the file itself, returning a Metadata instance for every found file. def recurse_local result = perform_recursion(self[:path]) return {} unless result result.inject({}) do |hash, meta| next hash if meta.relative_path == "." hash[meta.relative_path] = newchild(meta.relative_path) hash end end # Recurse against our remote file. def recurse_remote(children) sourceselect = self[:sourceselect] total = self[:source].collect do |source| next unless result = perform_recursion(source) return if top = result.find { |r| r.relative_path == "." } and top.ftype != "directory" result.each { |data| data.source = "#{source}/#{data.relative_path}" } break result if result and ! result.empty? and sourceselect == :first result end.flatten # This only happens if we have sourceselect == :all unless sourceselect == :first found = [] total.reject! do |data| result = found.include?(data.relative_path) found << data.relative_path unless found.include?(data.relative_path) result end end total.each do |meta| if meta.relative_path == "." parameter(:source).metadata = meta next end children[meta.relative_path] ||= newchild(meta.relative_path) children[meta.relative_path][:source] = meta.source children[meta.relative_path][:checksum] = :md5 if meta.ftype == "file" children[meta.relative_path].parameter(:source).metadata = meta end children end def perform_recursion(path) Puppet::FileServing::Metadata.indirection.search( path, :links => self[:links], :recurse => (self[:recurse] == :remote ? true : self[:recurse]), :recurselimit => self[:recurselimit], :ignore => self[:ignore], :checksum_type => (self[:source] || self[:content]) ? self[:checksum] : :none ) end # Remove any existing data. This is only used when dealing with # links or directories. def remove_existing(should) return unless s = stat self.fail "Could not back up; will not replace" unless perform_backup unless should.to_s == "link" return if s.ftype.to_s == should.to_s end case s.ftype when "directory" if self[:force] == :true debug "Removing existing directory for replacement with #{should}" FileUtils.rmtree(self[:path]) else notice "Not removing directory; use 'force' to override" return end when "link", "file" debug "Removing existing #{s.ftype} for replacement with #{should}" ::File.unlink(self[:path]) else self.fail "Could not back up files of type #{s.ftype}" end @stat = :needs_stat true end def retrieve if source = parameter(:source) source.copy_source_values end super end # Set the checksum, from another property. There are multiple # properties that modify the contents of a file, and they need the # ability to make sure that the checksum value is in sync. def setchecksum(sum = nil) if @parameters.include? :checksum if sum @parameters[:checksum].checksum = sum else # If they didn't pass in a sum, then tell checksum to # figure it out. currentvalue = @parameters[:checksum].retrieve @parameters[:checksum].checksum = currentvalue end end end # Should this thing be a normal file? This is a relatively complex # way of determining whether we're trying to create a normal file, # and it's here so that the logic isn't visible in the content property. def should_be_file? return true if self[:ensure] == :file # I.e., it's set to something like "directory" return false if e = self[:ensure] and e != :present # The user doesn't really care, apparently if self[:ensure] == :present return true unless s = stat return(s.ftype == "file" ? true : false) end # If we've gotten here, then :ensure isn't set return true if self[:content] return true if stat and stat.ftype == "file" false end # Stat our file. Depending on the value of the 'links' attribute, we # use either 'stat' or 'lstat', and we expect the properties to use the # resulting stat object accordingly (mostly by testing the 'ftype' # value). # # We use the initial value :needs_stat to ensure we only stat the file once, # but can also keep track of a failed stat (@stat == nil). This also allows # us to re-stat on demand by setting @stat = :needs_stat. def stat return @stat unless @stat == :needs_stat method = :stat # Files are the only types that support links if (self.class.name == :file and self[:links] != :follow) or self.class.name == :tidy method = :lstat end @stat = begin ::File.send(method, self[:path]) rescue Errno::ENOENT => error nil + rescue Errno::ENOTDIR => error + nil rescue Errno::EACCES => error warning "Could not stat; permission denied" nil end end # We have to hack this just a little bit, because otherwise we'll get # an error when the target and the contents are created as properties on # the far side. def to_trans(retrieve = true) obj = super obj.delete(:target) if obj[:target] == :notlink obj end # Write out the file. Requires the property name for logging. # Write will be done by the content property, along with checksum computation def write(property) remove_existing(:file) use_temporary_file = write_temporary_file? if use_temporary_file path = "#{self[:path]}.puppettmp_#{rand(10000)}" path = "#{self[:path]}.puppettmp_#{rand(10000)}" while ::File.exists?(path) or ::File.symlink?(path) else path = self[:path] end mode = self.should(:mode) # might be nil umask = mode ? 000 : 022 mode_int = mode ? symbolic_mode_to_int(mode, 0644) : nil content_checksum = Puppet::Util.withumask(umask) { ::File.open(path, 'wb', mode_int ) { |f| write_content(f) } } # And put our new file in place if use_temporary_file # This is only not true when our file is empty. begin fail_if_checksum_is_wrong(path, content_checksum) if validate_checksum? ::File.rename(path, self[:path]) rescue => detail fail "Could not rename temporary file #{path} to #{self[:path]}: #{detail}" ensure # Make sure the created file gets removed ::File.unlink(path) if FileTest.exists?(path) end end # make sure all of the modes are actually correct property_fix end private # Should we validate the checksum of the file we're writing? def validate_checksum? self[:checksum] !~ /time/ end # Make sure the file we wrote out is what we think it is. def fail_if_checksum_is_wrong(path, content_checksum) newsum = parameter(:checksum).sum_file(path) return if [:absent, nil, content_checksum].include?(newsum) self.fail "File written to disk did not match checksum; discarding changes (#{content_checksum} vs #{newsum})" end # write the current content. Note that if there is no content property # simply opening the file with 'w' as done in write is enough to truncate # or write an empty length file. def write_content(file) (content = property(:content)) && content.write(file) end private def write_temporary_file? # unfortunately we don't know the source file size before fetching it # so let's assume the file won't be empty (c = property(:content) and c.length) || (s = @parameters[:source] and 1) end # There are some cases where all of the work does not get done on # file creation/modification, so we have to do some extra checking. def property_fix properties.each do |thing| next unless [:mode, :owner, :group, :seluser, :selrole, :seltype, :selrange].include?(thing.name) # Make sure we get a new stat objct @stat = :needs_stat currentvalue = thing.retrieve thing.sync unless thing.safe_insync?(currentvalue) end end end # We put all of the properties in separate files, because there are so many # of them. The order these are loaded is important, because it determines # the order they are in the property lit. require 'puppet/type/file/checksum' require 'puppet/type/file/content' # can create the file require 'puppet/type/file/source' # can create the file require 'puppet/type/file/target' # creates a different type of file require 'puppet/type/file/ensure' # can create the file require 'puppet/type/file/owner' require 'puppet/type/file/group' require 'puppet/type/file/mode' require 'puppet/type/file/type' require 'puppet/type/file/selcontext' # SELinux file context require 'puppet/type/file/ctime' require 'puppet/type/file/mtime' diff --git a/lib/puppet/type/tidy.rb b/lib/puppet/type/tidy.rb index 224e79731..80e0f9018 100755 --- a/lib/puppet/type/tidy.rb +++ b/lib/puppet/type/tidy.rb @@ -1,333 +1,333 @@ Puppet::Type.newtype(:tidy) do require 'puppet/file_serving/fileset' require 'puppet/file_bucket/dipper' @doc = "Remove unwanted files based on specific criteria. Multiple criteria are OR'd together, so a file that is too large but is not old enough will still get tidied. If you don't specify either `age` or `size`, then all files will be removed. This resource type works by generating a file resource for every file that should be deleted and then letting that resource perform the actual deletion. " newparam(:path) do desc "The path to the file or directory to manage. Must be fully qualified." isnamevar end newparam(:recurse) do desc "If target is a directory, recursively descend into the directory looking for files to tidy." newvalues(:true, :false, :inf, /^[0-9]+$/) # Replace the validation so that we allow numbers in # addition to string representations of them. validate { |arg| } munge do |value| newval = super(value) case newval when :true, :inf; true when :false; false when Integer, Fixnum, Bignum; value when /^\d+$/; Integer(value) else raise ArgumentError, "Invalid recurse value #{value.inspect}" end end end newparam(:matches) do desc <<-EOT One or more (shell type) file glob patterns, which restrict the list of files to be tidied to those whose basenames match at least one of the patterns specified. Multiple patterns can be specified using an array. Example: tidy { "/tmp": age => "1w", recurse => 1, matches => [ "[0-9]pub*.tmp", "*.temp", "tmpfile?" ] } This removes files from `/tmp` if they are one week old or older, are not in a subdirectory and match one of the shell globs given. Note that the patterns are matched against the basename of each file -- that is, your glob patterns should not have any '/' characters in them, since you are only specifying against the last bit of the file. Finally, note that you must now specify a non-zero/non-false value for recurse if matches is used, as matches only apply to files found by recursion (there's no reason to use static patterns match against a statically determined path). Requiering explicit recursion clears up a common source of confusion. EOT # Make sure we convert to an array. munge do |value| fail "Tidy can't use matches with recurse 0, false, or undef" if "#{@resource[:recurse]}" =~ /^(0|false|)$/ [value].flatten end # Does a given path match our glob patterns, if any? Return true # if no patterns have been provided. def tidy?(path, stat) basename = File.basename(path) flags = File::FNM_DOTMATCH | File::FNM_PATHNAME return(value.find {|pattern| File.fnmatch(pattern, basename, flags) } ? true : false) end end newparam(:backup) do desc "Whether tidied files should be backed up. Any values are passed directly to the file resources used for actual file deletion, so consult the `file` type's backup documentation to determine valid values." end newparam(:age) do desc "Tidy files whose age is equal to or greater than the specified time. You can choose seconds, minutes, hours, days, or weeks by specifying the first letter of any of those words (e.g., '1w'). Specifying 0 will remove all files." @@ageconvertors = { :s => 1, :m => 60 } @@ageconvertors[:h] = @@ageconvertors[:m] * 60 @@ageconvertors[:d] = @@ageconvertors[:h] * 24 @@ageconvertors[:w] = @@ageconvertors[:d] * 7 def convert(unit, multi) if num = @@ageconvertors[unit] return num * multi else self.fail "Invalid age unit '#{unit}'" end end def tidy?(path, stat) # If the file's older than we allow, we should get rid of it. (Time.now.to_i - stat.send(resource[:type]).to_i) > value end munge do |age| unit = multi = nil case age when /^([0-9]+)(\w)\w*$/ multi = Integer($1) unit = $2.downcase.intern when /^([0-9]+)$/ multi = Integer($1) unit = :d else self.fail "Invalid tidy age #{age}" end convert(unit, multi) end end newparam(:size) do desc "Tidy files whose size is equal to or greater than the specified size. Unqualified values are in kilobytes, but *b*, *k*, *m*, *g*, and *t* can be appended to specify *bytes*, *kilobytes*, *megabytes*, *gigabytes*, and *terabytes*, respectively. Only the first character is significant, so the full word can also be used." @@sizeconvertors = { :b => 0, :k => 1, :m => 2, :g => 3, :t => 4 } def convert(unit, multi) if num = @@sizeconvertors[unit] result = multi num.times do result *= 1024 end return result else self.fail "Invalid size unit '#{unit}'" end end def tidy?(path, stat) stat.size >= value end munge do |size| case size when /^([0-9]+)(\w)\w*$/ multi = Integer($1) unit = $2.downcase.intern when /^([0-9]+)$/ multi = Integer($1) unit = :k else self.fail "Invalid tidy size #{age}" end convert(unit, multi) end end newparam(:type) do - desc "Set the mechanism for determining age." + desc "Set the mechanism for determining age. Default: atime." newvalues(:atime, :mtime, :ctime) defaultto :atime end newparam(:rmdirs, :boolean => true) do desc "Tidy directories in addition to files; that is, remove directories whose age is older than the specified criteria. This will only remove empty directories, so all contained files must also be tidied before a directory gets removed." newvalues :true, :false end # Erase PFile's validate method validate do end def self.instances [] end def depthfirst? true end def initialize(hash) super # only allow backing up into filebuckets self[:backup] = false unless self[:backup].is_a? Puppet::FileBucket::Dipper end # Make a file resource to remove a given file. def mkfile(path) # Force deletion, so directories actually get deleted. Puppet::Type.type(:file).new :path => path, :backup => self[:backup], :ensure => :absent, :force => true end def retrieve # Our ensure property knows how to retrieve everything for us. if obj = @parameters[:ensure] return obj.retrieve else return {} end end # Hack things a bit so we only ever check the ensure property. def properties [] end def generate return [] unless stat(self[:path]) case self[:recurse] when Integer, Fixnum, Bignum, /^\d+$/ parameter = { :recurse => true, :recurselimit => self[:recurse] } when true, :true, :inf parameter = { :recurse => true } end if parameter files = Puppet::FileServing::Fileset.new(self[:path], parameter).files.collect do |f| f == "." ? self[:path] : ::File.join(self[:path], f) end else files = [self[:path]] end result = files.find_all { |path| tidy?(path) }.collect { |path| mkfile(path) }.each { |file| notice "Tidying #{file.ref}" }.sort { |a,b| b[:path] <=> a[:path] } # No need to worry about relationships if we don't have rmdirs; there won't be # any directories. return result unless rmdirs? # Now make sure that all directories require the files they contain, if all are available, # so that a directory is emptied before we try to remove it. files_by_name = result.inject({}) { |hash, file| hash[file[:path]] = file; hash } files_by_name.keys.sort { |a,b| b <=> b }.each do |path| dir = ::File.dirname(path) next unless resource = files_by_name[dir] if resource[:require] resource[:require] << Puppet::Resource.new(:file, path) else resource[:require] = [Puppet::Resource.new(:file, path)] end end result end # Does a given path match our glob patterns, if any? Return true # if no patterns have been provided. def matches?(path) return true unless self[:matches] basename = File.basename(path) flags = File::FNM_DOTMATCH | File::FNM_PATHNAME if self[:matches].find {|pattern| File.fnmatch(pattern, basename, flags) } return true else debug "No specified patterns match #{path}, not tidying" return false end end # Should we remove the specified file? def tidy?(path) return false unless stat = self.stat(path) return false if stat.ftype == "directory" and ! rmdirs? # The 'matches' parameter isn't OR'ed with the other tests -- # it's just used to reduce the list of files we can match. return false if param = parameter(:matches) and ! param.tidy?(path, stat) tested = false [:age, :size].each do |name| next unless param = parameter(name) tested = true return true if param.tidy?(path, stat) end # If they don't specify either, then the file should always be removed. return true unless tested false end def stat(path) begin ::File.lstat(path) rescue Errno::ENOENT => error info "File does not exist" return nil rescue Errno::EACCES => error warning "Could not stat; permission denied" return nil end end end diff --git a/lib/puppet/util/autoload.rb b/lib/puppet/util/autoload.rb index 246ff5fc3..f20f16a79 100644 --- a/lib/puppet/util/autoload.rb +++ b/lib/puppet/util/autoload.rb @@ -1,158 +1,158 @@ require 'puppet/util/warnings' # Autoload paths, either based on names or all at once. class Puppet::Util::Autoload require 'puppet/util/autoload/file_cache' include Puppet::Util include Puppet::Util::Warnings include Puppet::Util::Autoload::FileCache @autoloaders = {} @loaded = [] class << self attr_reader :autoloaders private :autoloaders end # Send [], []=, and :clear to the @autloaders hash Puppet::Util.classproxy self, :autoloaders, "[]", "[]=" # List all loaded files. def self.list_loaded @loaded.sort { |a,b| a[0] <=> b[0] }.collect do |path, hash| "#{path}: #{hash[:file]}" end end # Has a given path been loaded? This is used for testing whether a # changed file should be loaded or just ignored. This is only # used in network/client/master, when downloading plugins, to # see if a given plugin is currently loaded and thus should be # reloaded. def self.loaded?(path) path = path.to_s.sub(/\.rb$/, '') @loaded.include?(path) end # Save the fact that a given path has been loaded. This is so # we can load downloaded plugins if they've already been loaded # into memory. def self.loaded(file) $" << file + ".rb" unless $".include?(file) @loaded << file unless @loaded.include?(file) end attr_accessor :object, :path, :objwarn, :wrap def initialize(obj, path, options = {}) @path = path.to_s raise ArgumentError, "Autoload paths cannot be fully qualified" if absolute_path?(@path) @object = obj self.class[obj] = self options.each do |opt, value| opt = opt.intern if opt.is_a? String begin self.send(opt.to_s + "=", value) rescue NoMethodError raise ArgumentError, "#{opt} is not a valid option" end end @wrap = true unless defined?(@wrap) end # Load a single plugin by name. We use 'load' here so we can reload a # given plugin. def load(name,env=nil) path = name.to_s + ".rb" searchpath(env).each do |dir| file = File.join(dir, path) next unless file_exist?(file) begin Kernel.load file, @wrap name = symbolize(name) loaded name, file return true rescue SystemExit,NoMemoryError raise rescue Exception => detail puts detail.backtrace if Puppet[:trace] raise Puppet::Error, "Could not autoload #{name}: #{detail}" end end false end # Mark the named object as loaded. Note that this supports unqualified # queries, while we store the result as a qualified query in the class. def loaded(name, file) self.class.loaded(File.join(@path, name.to_s)) end # Indicate whether the specfied plugin has been loaded. def loaded?(name) self.class.loaded?(File.join(@path, name.to_s)) end # Load all instances that we can. This uses require, rather than load, # so that already-loaded files don't get reloaded unnecessarily. def loadall # Load every instance of everything we can find. files_to_load.each do |file| name = File.basename(file).chomp(".rb").intern next if loaded?(name) begin - Kernel.require file loaded(name, file) + Kernel.require file rescue SystemExit,NoMemoryError raise rescue Exception => detail puts detail.backtrace if Puppet[:trace] raise Puppet::Error, "Could not autoload #{file}: #{detail}" end end end def files_to_load searchpath.map { |dir| Dir.glob("#{dir}/*.rb") }.flatten end # The list of directories to search through for loadable plugins. def searchpath(env=nil) search_directories(env).uniq.collect { |d| File.join(d, @path) }.find_all { |d| FileTest.directory?(d) } end def module_directories(env=nil) # We have to require this late in the process because otherwise we might # have load order issues. Since require is much slower than defined?, we # can skip that - and save some 2,155 invocations of require in my real # world testing. --daniel 2012-07-10 require 'puppet/node/environment' unless defined?(Puppet::Node::Environment) real_env = Puppet::Node::Environment.new(env) # We're using a per-thread cache of said module directories, so that # we don't scan the filesystem each time we try to load something with # this autoload instance. But since we don't want to cache for the eternity # this env_module_directories gets reset after the compilation on the master. # This is also reset after an agent ran. # One of the side effect of this change is that this module directories list will be # shared among all autoload that we have running at a time. But that won't be an issue # as by definition those directories are shared by all autoload. Thread.current[:env_module_directories] ||= {} Thread.current[:env_module_directories][real_env] ||= real_env.modulepath.collect do |dir| Dir.entries(dir).reject { |f| f =~ /^\./ }.collect { |f| File.join(dir, f) } end.flatten.collect { |d| [File.join(d, "plugins"), File.join(d, "lib")] }.flatten.find_all do |d| FileTest.directory?(d) end end def search_directories(env=nil) [module_directories(env), Puppet[:libdir].split(File::PATH_SEPARATOR), $LOAD_PATH].flatten end end diff --git a/spec/fixtures/unit/provider/service/openrc/rcservice_list b/spec/fixtures/unit/provider/service/openrc/rcservice_list new file mode 100644 index 000000000..f413a566c --- /dev/null +++ b/spec/fixtures/unit/provider/service/openrc/rcservice_list @@ -0,0 +1,8 @@ +alsasound +consolefont +lvm-monitoring +pydoc-2.7 +pydoc-3.2 +wpa_supplicant +xdm +xdm-setup diff --git a/spec/fixtures/unit/provider/service/openrc/rcstatus b/spec/fixtures/unit/provider/service/openrc/rcstatus new file mode 100644 index 000000000..1406671a4 --- /dev/null +++ b/spec/fixtures/unit/provider/service/openrc/rcstatus @@ -0,0 +1,43 @@ +Runlevel: boot + hwclock [ started ] + modules [ started ] + dmcrypt [ started ] + lvm [ started ] + fsck [ started ] + root [ started ] + mtab [ started ] + swap [ started ] + localmount [ started ] + sysctl [ started ] + bootmisc [ started ] + hostname [ started ] + termencoding [ started ] + keymaps [ started ] + net.lo [ started ] + procfs [ started ] + rsyslog [ started ] + swapfiles [ started ] + urandom [ started ] +Runlevel: default + netmount [ started ] + foo_with_very_very_long_servicename_no_still_not_the_end_wait_for_it_almost_there_almost_there_now_finally_the_end [ started ] + xdm [ started ] + alsasound [ started ] + udev-postmount [ started ] + local [ started ] +Runlevel: shutdown + killprocs [ stopped ] + savecache [ stopped ] + mount-ro [ stopped ] +Runlevel: sysinit + dmesg [ started ] + udev [ started ] + devfs [ started ] +Dynamic Runlevel: hotplugged + net.eth0 [ started ] + pcscd [ started ] +Dynamic Runlevel: needed + sysfs [ started ] + udev-mount [ started ] +Dynamic Runlevel: manual + sshd [ started ] diff --git a/spec/integration/defaults_spec.rb b/spec/integration/defaults_spec.rb index 6474ea2e8..6e0b504a2 100755 --- a/spec/integration/defaults_spec.rb +++ b/spec/integration/defaults_spec.rb @@ -1,317 +1,317 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/defaults' require 'puppet/rails' describe "Puppet defaults" do include Puppet::Util::Execution after { Puppet.settings.clear } describe "when setting the :factpath" do it "should add the :factpath to Facter's search paths" do Facter.expects(:search).with("/my/fact/path") Puppet.settings[:factpath] = "/my/fact/path" end end describe "when setting the :certname" do it "should fail if the certname is not downcased" do - lambda { Puppet.settings[:certname] = "Host.Domain.Com" }.should raise_error(ArgumentError) + expect { Puppet.settings[:certname] = "Host.Domain.Com" }.to raise_error(ArgumentError) end end describe "when setting :node_name_value" do it "should default to the value of :certname" do Puppet.settings[:certname] = 'blargle' Puppet.settings[:node_name_value].should == 'blargle' end end describe "when setting the :node_name_fact" do it "should fail when also setting :node_name_value" do lambda do Puppet.settings[:node_name_value] = "some value" Puppet.settings[:node_name_fact] = "some_fact" end.should raise_error("Cannot specify both the node_name_value and node_name_fact settings") end it "should not fail when using the default for :node_name_value" do lambda do Puppet.settings[:node_name_fact] = "some_fact" end.should_not raise_error end end describe "when :certdnsnames is set" do it "should not fail" do - expect { Puppet[:certdnsnames] = 'fred:wilma' }.should_not raise_error + expect { Puppet[:certdnsnames] = 'fred:wilma' }.to_not raise_error end it "should warn the value is ignored" do Puppet.expects(:warning).with {|msg| msg =~ /CVE-2011-3872/ } Puppet[:certdnsnames] = 'fred:wilma' end end describe "when configuring the :crl" do it "should warn if :cacrl is set to false" do Puppet.expects(:warning) Puppet.settings[:cacrl] = 'false' end end describe "when setting the :catalog_format" do it "should log a deprecation notice" do Puppet.expects(:warning) Puppet.settings[:catalog_format] = 'marshal' end it "should copy the value to :preferred_serialization_format" do Puppet.settings[:catalog_format] = 'marshal' Puppet.settings[:preferred_serialization_format].should == 'marshal' end end it "should have a clientyamldir setting" do Puppet.settings[:clientyamldir].should_not be_nil end it "should have different values for the yamldir and clientyamldir" do Puppet.settings[:yamldir].should_not == Puppet.settings[:clientyamldir] end it "should have a client_datadir setting" do Puppet.settings[:client_datadir].should_not be_nil end it "should have different values for the server_datadir and client_datadir" do Puppet.settings[:server_datadir].should_not == Puppet.settings[:client_datadir] end # See #1232 it "should not specify a user or group for the clientyamldir" do Puppet.settings.setting(:clientyamldir).owner.should be_nil Puppet.settings.setting(:clientyamldir).group.should be_nil end it "should use the service user and group for the yamldir" do Puppet.settings.stubs(:service_user_available?).returns true Puppet.settings.setting(:yamldir).owner.should == Puppet.settings[:user] Puppet.settings.setting(:yamldir).group.should == Puppet.settings[:group] end # See #1232 it "should not specify a user or group for the rundir" do Puppet.settings.setting(:rundir).owner.should be_nil Puppet.settings.setting(:rundir).group.should be_nil end it "should specify that the host private key should be owned by the service user" do Puppet.settings.stubs(:service_user_available?).returns true Puppet.settings.setting(:hostprivkey).owner.should == Puppet.settings[:user] end it "should specify that the host certificate should be owned by the service user" do Puppet.settings.stubs(:service_user_available?).returns true Puppet.settings.setting(:hostcert).owner.should == Puppet.settings[:user] end it "should use a bind address of ''" do Puppet.settings.clear Puppet.settings[:bindaddress].should == "" end [:factdest].each do |setting| it "should force the :factdest to be a directory" do Puppet.settings[setting].should =~ /\/$/ end end [:modulepath, :factpath].each do |setting| it "should configure '#{setting}' not to be a file setting, so multi-directory settings are acceptable" do Puppet.settings.setting(setting).should be_instance_of(Puppet::Util::Settings::Setting) end end it "should add /usr/sbin and /sbin to the path if they're not there" do withenv("PATH" => "/usr/bin:/usr/local/bin") do Puppet.settings[:path] = "none" # this causes it to ignore the setting ENV["PATH"].split(File::PATH_SEPARATOR).should be_include("/usr/sbin") ENV["PATH"].split(File::PATH_SEPARATOR).should be_include("/sbin") end end it "should default to pson for the preferred serialization format" do Puppet.settings.value(:preferred_serialization_format).should == "pson" end describe "when enabling storeconfigs" do before do Puppet::Resource::Catalog.indirection.stubs(:cache_class=) Puppet::Node::Facts.indirection.stubs(:cache_class=) Puppet::Node.indirection.stubs(:cache_class=) Puppet.features.stubs(:rails?).returns true end it "should set the Catalog cache class to :store_configs" do Puppet::Resource::Catalog.indirection.expects(:cache_class=).with(:store_configs) Puppet.settings[:storeconfigs] = true end it "should not set the Catalog cache class to :store_configs if asynchronous storeconfigs is enabled" do Puppet::Resource::Catalog.indirection.expects(:cache_class=).with(:store_configs).never Puppet.settings.expects(:value).with(:async_storeconfigs).returns true Puppet.settings[:storeconfigs] = true end it "should set the Facts cache class to :store_configs" do Puppet::Node::Facts.indirection.expects(:cache_class=).with(:store_configs) Puppet.settings[:storeconfigs] = true end it "should set the Node cache class to :store_configs" do Puppet::Node.indirection.expects(:cache_class=).with(:store_configs) Puppet.settings[:storeconfigs] = true end end describe "when enabling asynchronous storeconfigs" do before do Puppet::Resource::Catalog.indirection.stubs(:cache_class=) Puppet::Node::Facts.indirection.stubs(:cache_class=) Puppet::Node.indirection.stubs(:cache_class=) Puppet.features.stubs(:rails?).returns true end it "should set storeconfigs to true" do Puppet.settings[:async_storeconfigs] = true Puppet.settings[:storeconfigs].should be_true end it "should set the Catalog cache class to :queue" do Puppet::Resource::Catalog.indirection.expects(:cache_class=).with(:queue) Puppet.settings[:async_storeconfigs] = true end it "should set the Facts cache class to :store_configs" do Puppet::Node::Facts.indirection.expects(:cache_class=).with(:store_configs) Puppet.settings[:storeconfigs] = true end it "should set the Node cache class to :store_configs" do Puppet::Node.indirection.expects(:cache_class=).with(:store_configs) Puppet.settings[:storeconfigs] = true end end describe "when enabling thin storeconfigs" do before do Puppet::Resource::Catalog.indirection.stubs(:cache_class=) Puppet::Node::Facts.indirection.stubs(:cache_class=) Puppet::Node.indirection.stubs(:cache_class=) Puppet.features.stubs(:rails?).returns true end it "should set storeconfigs to true" do Puppet.settings[:thin_storeconfigs] = true Puppet.settings[:storeconfigs].should be_true end end it "should have a setting for determining the configuration version and should default to an empty string" do Puppet.settings[:config_version].should == "" end describe "when enabling reports" do it "should use the default server value when report server is unspecified" do Puppet.settings[:server] = "server" Puppet.settings[:report_server].should == "server" end it "should use the default masterport value when report port is unspecified" do Puppet.settings[:masterport] = "1234" Puppet.settings[:report_port].should == "1234" end it "should set report_server when reportserver is set" do Puppet.settings[:reportserver] = "reportserver" Puppet.settings[:report_server].should == "reportserver" end it "should use report_port when set" do Puppet.settings[:masterport] = "1234" Puppet.settings[:report_port] = "5678" Puppet.settings[:report_port].should == "5678" end it "should prefer report_server over reportserver" do Puppet.settings[:reportserver] = "reportserver" Puppet.settings[:report_server] = "report_server" Puppet.settings[:report_server].should == "report_server" end end it "should have a :caname setting that defaults to the cert name" do Puppet.settings[:certname] = "foo" Puppet.settings[:ca_name].should == "Puppet CA: foo" end it "should have a 'prerun_command' that defaults to the empty string" do Puppet.settings[:prerun_command].should == "" end it "should have a 'postrun_command' that defaults to the empty string" do Puppet.settings[:postrun_command].should == "" end it "should have a 'certificate_revocation' setting that defaults to true" do Puppet.settings[:certificate_revocation].should be_true end it "should have an http_compression setting that defaults to false" do Puppet.settings[:http_compression].should be_false end describe "reportdir" do subject { Puppet.settings[:reportdir] } it { should == "#{Puppet[:vardir]}/reports" } end describe "reporturl" do subject { Puppet.settings[:reporturl] } it { should == "http://localhost:3000/reports/upload" } end describe "when configuring color" do subject { Puppet.settings[:color] } it { should == "ansi" } end describe "daemonize" do it "should default to true", :unless => Puppet.features.microsoft_windows? do Puppet.settings[:daemonize].should == true end describe "on Windows", :if => Puppet.features.microsoft_windows? do it "should default to false" do Puppet.settings[:daemonize].should == false end it "should raise an error if set to true" do - lambda { Puppet.settings[:daemonize] = true }.should raise_error(/Cannot daemonize on Windows/) + expect { Puppet.settings[:daemonize] = true }.to raise_error(/Cannot daemonize on Windows/) end end end describe "diff" do it "should default to 'diff' on POSIX", :unless => Puppet.features.microsoft_windows? do Puppet.settings[:diff].should == 'diff' end it "should default to '' on Windows", :if => Puppet.features.microsoft_windows? do Puppet.settings[:diff].should == '' end end end diff --git a/spec/integration/network/server/mongrel_spec.rb b/spec/integration/network/server/mongrel_spec.rb index a92ba83a1..44f20c30b 100755 --- a/spec/integration/network/server/mongrel_spec.rb +++ b/spec/integration/network/server/mongrel_spec.rb @@ -1,70 +1,72 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/network/server' require 'net/http' describe Puppet::Network::Server, :'fails_on_ruby_1.9.2' => true do describe "when using mongrel", :if => Puppet.features.mongrel? do # This reduces the odds of conflicting port numbers between concurrent runs # of the suite on the same machine dramatically. def port 20001 + ($$ % 40000) end before :each do Puppet[:servertype] = 'mongrel' Puppet[:server] = '127.0.0.1' @params = { :port => port, :handlers => [ :node ] } @server = Puppet::Network::Server.new(@params) end after :each do @server.unlisten if @server.listening? end describe "before listening" do it "should not be reachable at the specified address and port" do - lambda { Net::HTTP.get('127.0.0.1', '/', port) }. - should raise_error(Errno::ECONNREFUSED) + expect { + Net::HTTP.get('127.0.0.1', '/', port) + }.to raise_error(Errno::ECONNREFUSED) end end describe "when listening" do it "should be reachable on the specified address and port" do @server.listen - expect { Net::HTTP.get('127.0.0.1', '/', port) }.should_not raise_error + expect { Net::HTTP.get('127.0.0.1', '/', port) }.to_not raise_error end it "should default to '127.0.0.1' as its bind address" do @server = Puppet::Network::Server.new(@params.merge(:port => 34343)) @server.stubs(:unlisten) # we're breaking listening internally, so we have to keep it from unlistening @server.send(:http_server).expects(:listen).with { |args| args[:address] == "127.0.0.1" } @server.listen end it "should use any specified bind address" do Puppet[:bindaddress] = "0.0.0.0" @server = Puppet::Network::Server.new(@params.merge(:port => 34343)) @server.stubs(:unlisten) # we're breaking listening internally, so we have to keep it from unlistening @server.send(:http_server).expects(:listen).with { |args| args[:address] == "0.0.0.0" } @server.listen end it "should not allow multiple servers to listen on the same address and port" do @server.listen @server2 = Puppet::Network::Server.new(@params) - lambda { @server2.listen }.should raise_error + expect { @server2.listen }.to raise_error end end describe "after unlistening" do it "should not be reachable on the port and address assigned" do @server.listen @server.unlisten - expect { Net::HTTP.get('127.0.0.1', '/', port) }. - should raise_error Errno::ECONNREFUSED + expect { + Net::HTTP.get('127.0.0.1', '/', port) + }.to raise_error Errno::ECONNREFUSED end end end end diff --git a/spec/integration/parser/parser_spec.rb b/spec/integration/parser/parser_spec.rb index f6abdb274..06607eec0 100755 --- a/spec/integration/parser/parser_spec.rb +++ b/spec/integration/parser/parser_spec.rb @@ -1,150 +1,150 @@ #!/usr/bin/env rspec require 'spec_helper' describe Puppet::Parser::Parser do module ParseMatcher class ParseAs def initialize(klass) @parser = Puppet::Parser::Parser.new "development" @class = klass end def result_instance @result.code[0] end def matches?(string) @string = string @result = @parser.parse(string) result_instance.instance_of?(@class) end def description "parse as a #{@class}" end def failure_message " expected #{@string} to parse as #{@class} but was #{result_instance.class}" end def negative_failure_message " expected #{@string} not to parse as #{@class}" end end def parse_as(klass) ParseAs.new(klass) end class ParseWith def initialize(block) @parser = Puppet::Parser::Parser.new "development" @block = block end def result_instance @result.code[0] end def matches?(string) @string = string @result = @parser.parse(string) @block.call(result_instance) end def description "parse with the block evaluating to true" end def failure_message " expected #{@string} to parse with a true result in the block" end def negative_failure_message " expected #{@string} not to parse with a true result in the block" end end def parse_with(&block) ParseWith.new(block) end end include ParseMatcher before :each do @resource_type_collection = Puppet::Resource::TypeCollection.new("env") @parser = Puppet::Parser::Parser.new "development" end describe "when parsing comments before statement" do it "should associate the documentation to the statement AST node" do ast = @parser.parse(""" # comment class test {} """) ast.code[0].should be_a(Puppet::Parser::AST::Hostclass) ast.code[0].name.should == 'test' ast.code[0].instantiate('')[0].doc.should == "comment\n" end end describe "when parsing" do it "should be able to parse normal left to right relationships" do "Notify[foo] -> Notify[bar]".should parse_as(Puppet::Parser::AST::Relationship) end it "should be able to parse right to left relationships" do "Notify[foo] <- Notify[bar]".should parse_as(Puppet::Parser::AST::Relationship) end it "should be able to parse normal left to right subscriptions" do "Notify[foo] ~> Notify[bar]".should parse_as(Puppet::Parser::AST::Relationship) end it "should be able to parse right to left subscriptions" do "Notify[foo] <~ Notify[bar]".should parse_as(Puppet::Parser::AST::Relationship) end it "should correctly set the arrow type of a relationship" do "Notify[foo] <~ Notify[bar]".should parse_with { |rel| rel.arrow == "<~" } end it "should be able to parse deep hash access" do %q{ $hash = { 'a' => { 'b' => { 'c' => 'it works' } } } $out = $hash['a']['b']['c'] }.should parse_with { |v| v.value.is_a?(Puppet::Parser::AST::ASTHash) } end it "should fail if asked to parse '$foo::::bar'" do - expect { @parser.parse("$foo::::bar") }.should raise_error(Puppet::ParseError, /Syntax error at ':'/) + expect { @parser.parse("$foo::::bar") }.to raise_error(Puppet::ParseError, /Syntax error at ':'/) end describe "function calls" do it "should be able to pass an array to a function" do "my_function([1,2,3])".should parse_with { |fun| fun.is_a?(Puppet::Parser::AST::Function) && fun.arguments[0].evaluate(stub 'scope') == ['1','2','3'] } end it "should be able to pass a hash to a function" do "my_function({foo => bar})".should parse_with { |fun| fun.is_a?(Puppet::Parser::AST::Function) && fun.arguments[0].evaluate(stub 'scope') == {'foo' => 'bar'} } end end describe "collections" do it "should find resources according to an expression" do %q{ File <| mode == 0700 + 0050 + 0050 |> }.should parse_with { |coll| coll.is_a?(Puppet::Parser::AST::Collection) && coll.query.evaluate(stub 'scope').first == ["mode", "==", 0700 + 0050 + 0050] } end end end end diff --git a/spec/integration/type/file_spec.rb b/spec/integration/type/file_spec.rb index 232debacb..1b7b87ced 100755 --- a/spec/integration/type/file_spec.rb +++ b/spec/integration/type/file_spec.rb @@ -1,1068 +1,1105 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet_spec/files' if Puppet.features.microsoft_windows? require 'puppet/util/windows' class WindowsSecurity extend Puppet::Util::Windows::Security end end describe Puppet::Type.type(:file) do include PuppetSpec::Files let(:catalog) { Puppet::Resource::Catalog.new } - let(:path) { tmpfile('file_testing') } + let(:path) do + # we create a directory first so backups of :path that are stored in + # the same directory will also be removed after the tests + parent = tmpdir('file_spec') + File.join(parent, 'file_testing') + end if Puppet.features.posix? def set_mode(mode, file) File.chmod(mode, file) end def get_mode(file) File.lstat(file).mode end def get_owner(file) File.lstat(file).uid end def get_group(file) File.lstat(file).gid end else class SecurityHelper extend Puppet::Util::Windows::Security end def set_mode(mode, file) SecurityHelper.set_mode(mode, file) end def get_mode(file) SecurityHelper.get_mode(file) end def get_owner(file) SecurityHelper.get_owner(file) end def get_group(file) SecurityHelper.get_group(file) end end before do # stub this to not try to create state.yaml Puppet::Util::Storage.stubs(:store) end it "should not attempt to manage files that do not exist if no means of creating the file is specified" do file = described_class.new :path => path, :mode => 0755 catalog.add_resource file file.parameter(:mode).expects(:retrieve).never - report = catalog.apply.report - report.resource_statuses["File[#{path}]"].should_not be_failed + status = catalog.apply.report.resource_statuses["File[#{path}]"] + status.should_not be_failed + status.should_not be_changed File.should_not be_exist(path) end + describe "when ensure is absent" do + it "should remove the file if present" do + FileUtils.touch(path) + catalog.add_resource(described_class.new(:path => path, :ensure => :absent, :backup => :false)) + report = catalog.apply.report + report.resource_statuses["File[#{path}]"].should_not be_failed + File.should_not be_exist(path) + end + + it "should do nothing if file is not present" do + catalog.add_resource(described_class.new(:path => path, :ensure => :absent, :backup => :false)) + report = catalog.apply.report + report.resource_statuses["File[#{path}]"].should_not be_failed + File.should_not be_exist(path) + end + + # issue #14599 + it "should not fail if parts of path aren't directories" do + FileUtils.touch(path) + catalog.add_resource(described_class.new(:path => File.join(path,'no_such_file'), :ensure => :absent, :backup => :false)) + report = catalog.apply.report + report.resource_statuses["File[#{File.join(path,'no_such_file')}]"].should_not be_failed + end + end + describe "when setting permissions" do it "should set the owner" do FileUtils.touch(path) owner = get_owner(path) file = described_class.new( :name => path, :owner => owner ) catalog.add_resource file catalog.apply get_owner(path).should == owner end it "should set the group" do FileUtils.touch(path) group = get_group(path) file = described_class.new( :name => path, :group => group ) catalog.add_resource file catalog.apply get_group(path).should == group end describe "when setting mode" do describe "for directories" do let(:path) { tmpdir('dir_mode') } it "should set executable bits for newly created directories" do catalog.add_resource described_class.new(:path => path, :ensure => :directory, :mode => 0600) catalog.apply (get_mode(path) & 07777).should == 0700 end it "should set executable bits for existing readable directories" do File.should be_directory(path) set_mode(0600, path) catalog.add_resource described_class.new(:path => path, :ensure => :directory, :mode => 0644) catalog.apply (get_mode(path) & 07777).should == 0755 end it "should not set executable bits for unreadable directories" do begin catalog.add_resource described_class.new(:path => path, :ensure => :directory, :mode => 0300) catalog.apply (get_mode(path) & 07777).should == 0300 ensure # so we can cleanup set_mode(0700, path) end end it "should set user, group, and other executable bits" do catalog.add_resource described_class.new(:path => path, :ensure => :directory, :mode => 0664) catalog.apply (get_mode(path) & 07777).should == 0775 end it "should set executable bits when overwriting a non-executable file" do FileUtils.rmdir(path) FileUtils.touch(path) set_mode(0444, path) catalog.add_resource described_class.new(:path => path, :ensure => :directory, :mode => 0666, :backup => false) catalog.apply (get_mode(path) & 07777).should == 0777 end end describe "for files" do let(:path) { tmpfile('file_mode') } it "should not set executable bits" do catalog.add_resource described_class.new(:path => path, :ensure => :file, :mode => 0666) catalog.apply (get_mode(path) & 07777).should == 0666 end it "should not set executable bits when replacing an executable directory (#10365)" do pending("bug #10365") FileUtils.mkdir(path) set_mode(0777, path) catalog.add_resource described_class.new(:path => path, :ensure => :file, :mode => 0666, :backup => false, :force => true) catalog.apply (get_mode(path) & 07777).should == 0666 end end describe "for links", :unless => Puppet.features.microsoft_windows? do let(:link) { tmpfile('link_mode') } describe "when managing links" do let(:target) { tmpfile('target') } - before :each do + it "should not set the executable bit on the link nor the target" do FileUtils.touch(target) File.chmod(0444, target) - File.symlink(target, link) - end - it "should not set the executable bit on the link nor the target" do catalog.add_resource described_class.new(:path => link, :ensure => :link, :mode => 0666, :target => target, :links => :manage) catalog.apply (File.stat(link).mode & 07777) == 0666 (File.lstat(target).mode & 07777) == 0444 end it "should ignore dangling symlinks (#6856)" do - File.delete(target) + File.symlink(target, link) catalog.add_resource described_class.new(:path => link, :ensure => :link, :mode => 0666, :target => target, :links => :manage) catalog.apply File.should_not be_exist(link) end + + it "should create a link to the target if ensure is omitted" do + FileUtils.touch(target) + catalog.add_resource described_class.new(:path => link, :target => target) + catalog.apply + + File.should be_exist link + File.lstat(link).ftype.should == 'link' + File.readlink(link).should == target + end end describe "when following links" do it "should ignore dangling symlinks (#6856)" do target = tmpfile('dangling') FileUtils.touch(target) File.symlink(target, link) File.delete(target) catalog.add_resource described_class.new(:path => path, :source => link, :mode => 0600, :links => :follow) catalog.apply end describe "to a directory" do let(:target) { tmpdir('dir_target') } before :each do File.chmod(0600, target) File.symlink(target, link) end after :each do File.chmod(0750, target) end describe "that is readable" do it "should set the executable bits when creating the destination (#10315)" do pending "bug #10315" catalog.add_resource described_class.new(:path => path, :source => link, :mode => 0666, :links => :follow) catalog.apply (get_mode(path) & 07777).should == 0777 end it "should set the executable bits when overwriting the destination (#10315)" do pending "bug #10315" FileUtils.touch(path) catalog.add_resource described_class.new(:path => path, :source => link, :mode => 0666, :links => :follow) catalog.apply (get_mode(path) & 07777).should == 0777 end end describe "that is not readable" do before :each do set_mode(0300, target) end # so we can cleanup after :each do set_mode(0700, target) end it "should not set executable bits when creating the destination (#10315)" do pending "bug #10315" catalog.add_resource described_class.new(:path => path, :source => link, :mode => 0666, :links => :follow) catalog.apply (get_mode(path) & 07777).should == 0666 end it "should not set executable bits when overwriting the destination" do FileUtils.touch(path) catalog.add_resource described_class.new(:path => path, :source => link, :mode => 0666, :links => :follow) catalog.apply (get_mode(path) & 07777).should == 0666 end end end describe "to a file" do let(:target) { tmpfile('file_target') } it "should create the file, not a symlink (#2817, #10315)" do pending "bug #2817, #10315" catalog.add_resource described_class.new(:path => path, :source => link, :mode => 0600, :links => :follow) catalog.apply File.should be_file(path) (get_mode(path) & 07777) == 0600 end it "should overwrite the file" do FileUtils.touch(path) catalog.add_resource described_class.new(:path => path, :source => link, :mode => 0600, :links => :follow) catalog.apply File.should be_file(path) (get_mode(path) & 07777) == 0600 end end describe "to a link to a directory" do let(:real_target) { tmpdir('real_target') } let(:target) { tmpfile('target') } before :each do File.chmod(0666, real_target) # link -> target -> real_target File.symlink(real_target, target) File.symlink(target, link) end after :each do File.chmod(0750, real_target) end describe "when following all links" do it "should create the destination and apply executable bits (#10315)" do pending "bug #10315" catalog.add_resource described_class.new(:path => path, :source => link, :mode => 0600, :links => :follow) catalog.apply File.should be_directory(path) (get_mode(path) & 07777) == 0777 end it "should overwrite the destination and apply executable bits" do FileUtils.mkdir(path) catalog.add_resource described_class.new(:path => path, :source => link, :mode => 0600, :links => :follow) catalog.apply File.should be_directory(path) (get_mode(path) & 07777) == 0777 end end end end end end end describe "when writing files" do it "should backup files to a filebucket when one is configured" do filebucket = Puppet::Type.type(:filebucket).new :path => tmpfile("filebucket"), :name => "mybucket" file = described_class.new :path => path, :backup => "mybucket", :content => "foo" catalog.add_resource file catalog.add_resource filebucket File.open(file[:path], "wb") { |f| f.puts "bar" } md5 = Digest::MD5.hexdigest(IO.binread(file[:path])) catalog.apply filebucket.bucket.getfile(md5).should == "bar\n" end it "should backup files in the local directory when a backup string is provided" do file = described_class.new :path => path, :backup => ".bak", :content => "foo" catalog.add_resource file File.open(file[:path], "w") { |f| f.puts "bar" } catalog.apply backup = file[:path] + ".bak" FileTest.should be_exist(backup) File.read(backup).should == "bar\n" end it "should fail if no backup can be performed" do - dir = tmpfile("backups") - Dir.mkdir(dir) + dir = tmpdir("backups") file = described_class.new :path => File.join(dir, "testfile"), :backup => ".bak", :content => "foo" catalog.add_resource file File.open(file[:path], 'w') { |f| f.puts "bar" } # Create a directory where the backup should be so that writing to it fails Dir.mkdir(File.join(dir, "testfile.bak")) Puppet::Util::Log.stubs(:newmessage) catalog.apply File.read(file[:path]).should == "bar\n" end it "should not backup symlinks", :unless => Puppet.features.microsoft_windows? do link = tmpfile("link") dest1 = tmpfile("dest1") dest2 = tmpfile("dest2") bucket = Puppet::Type.type(:filebucket).new :path => tmpfile("filebucket"), :name => "mybucket" file = described_class.new :path => link, :target => dest2, :ensure => :link, :backup => "mybucket" catalog.add_resource file catalog.add_resource bucket File.open(dest1, "w") { |f| f.puts "whatever" } File.symlink(dest1, link) md5 = Digest::MD5.hexdigest(File.read(file[:path])) catalog.apply File.readlink(link).should == dest2 Find.find(bucket[:path]) { |f| File.file?(f) }.should be_nil end it "should backup directories to the local filesystem by copying the whole directory" do file = described_class.new :path => path, :backup => ".bak", :content => "foo", :force => true catalog.add_resource file Dir.mkdir(path) otherfile = File.join(path, "foo") File.open(otherfile, "w") { |f| f.print "yay" } catalog.apply backup = "#{path}.bak" FileTest.should be_directory(backup) File.read(File.join(backup, "foo")).should == "yay" end it "should backup directories to filebuckets by backing up each file separately" do bucket = Puppet::Type.type(:filebucket).new :path => tmpfile("filebucket"), :name => "mybucket" file = described_class.new :path => tmpfile("bucket_backs"), :backup => "mybucket", :content => "foo", :force => true catalog.add_resource file catalog.add_resource bucket Dir.mkdir(file[:path]) foofile = File.join(file[:path], "foo") barfile = File.join(file[:path], "bar") File.open(foofile, "w") { |f| f.print "fooyay" } File.open(barfile, "w") { |f| f.print "baryay" } foomd5 = Digest::MD5.hexdigest(File.read(foofile)) barmd5 = Digest::MD5.hexdigest(File.read(barfile)) catalog.apply bucket.bucket.getfile(foomd5).should == "fooyay" bucket.bucket.getfile(barmd5).should == "baryay" end it "should propagate failures encountered when renaming the temporary file" do file = described_class.new :path => path, :content => "foo" file.stubs(:perform_backup).returns(true) catalog.add_resource file File.open(path, "w") { |f| f.print "bar" } File.expects(:rename).raises ArgumentError expect { file.write(:content) }.to raise_error(Puppet::Error, /Could not rename temporary file/) File.read(path).should == "bar" end end describe "when recursing" do def build_path(dir) Dir.mkdir(dir) File.chmod(0750, dir) @dirs = [dir] @files = [] %w{one two}.each do |subdir| fdir = File.join(dir, subdir) Dir.mkdir(fdir) File.chmod(0750, fdir) @dirs << fdir %w{three}.each do |file| ffile = File.join(fdir, file) @files << ffile File.open(ffile, "w") { |f| f.puts "test #{file}" } File.chmod(0640, ffile) end end end it "should be able to recurse over a nonexistent file" do @file = described_class.new( :name => path, :mode => 0644, :recurse => true, :backup => false ) catalog.add_resource @file lambda { @file.eval_generate }.should_not raise_error end it "should be able to recursively set properties on existing files" do path = tmpfile("file_integration_tests") build_path(path) file = described_class.new( :name => path, :mode => 0644, :recurse => true, :backup => false ) catalog.add_resource file catalog.apply @dirs.should_not be_empty @dirs.each do |path| (get_mode(path) & 007777).should == 0755 end @files.should_not be_empty @files.each do |path| (get_mode(path) & 007777).should == 0644 end end it "should be able to recursively make links to other files", :unless => Puppet.features.microsoft_windows? do source = tmpfile("file_link_integration_source") build_path(source) dest = tmpfile("file_link_integration_dest") @file = described_class.new(:name => dest, :target => source, :recurse => true, :ensure => :link, :backup => false) catalog.add_resource @file catalog.apply @dirs.each do |path| link_path = path.sub(source, dest) File.lstat(link_path).should be_directory end @files.each do |path| link_path = path.sub(source, dest) File.lstat(link_path).ftype.should == "link" end end it "should be able to recursively copy files" do source = tmpfile("file_source_integration_source") build_path(source) dest = tmpfile("file_source_integration_dest") @file = described_class.new(:name => dest, :source => source, :recurse => true, :backup => false) catalog.add_resource @file catalog.apply @dirs.each do |path| newpath = path.sub(source, dest) File.lstat(newpath).should be_directory end @files.each do |path| newpath = path.sub(source, dest) File.lstat(newpath).ftype.should == "file" end end it "should not recursively manage files managed by a more specific explicit file" do dir = tmpfile("recursion_vs_explicit_1") subdir = File.join(dir, "subdir") file = File.join(subdir, "file") FileUtils.mkdir_p(subdir) File.open(file, "w") { |f| f.puts "" } base = described_class.new(:name => dir, :recurse => true, :backup => false, :mode => "755") sub = described_class.new(:name => subdir, :recurse => true, :backup => false, :mode => "644") catalog.add_resource base catalog.add_resource sub catalog.apply (get_mode(file) & 007777).should == 0644 end it "should recursively manage files even if there is an explicit file whose name is a prefix of the managed file" do managed = File.join(path, "file") generated = File.join(path, "file_with_a_name_starting_with_the_word_file") managed_mode = 0700 FileUtils.mkdir_p(path) FileUtils.touch(managed) FileUtils.touch(generated) catalog.add_resource described_class.new(:name => path, :recurse => true, :backup => false, :mode => managed_mode) catalog.add_resource described_class.new(:name => managed, :recurse => true, :backup => false, :mode => "644") catalog.apply (get_mode(generated) & 007777).should == managed_mode end describe "when recursing remote directories" do describe "when sourceselect first" do describe "for a directory" do it "should recursively copy the first directory that exists" do one = File.expand_path('thisdoesnotexist') two = tmpdir('two') FileUtils.mkdir_p(File.join(two, 'three')) FileUtils.touch(File.join(two, 'three', 'four')) obj = Puppet::Type.newfile( :path => path, :ensure => :directory, :backup => false, :recurse => true, :sourceselect => :first, :source => [one, two] ) catalog.add_resource obj catalog.apply File.should be_directory(path) File.should_not be_exist(File.join(path, 'one')) File.should be_exist(File.join(path, 'three', 'four')) end it "should recursively copy an empty directory" do one = File.expand_path('thisdoesnotexist') two = tmpdir('two') three = tmpdir('three') FileUtils.mkdir_p(two) FileUtils.mkdir_p(three) FileUtils.touch(File.join(three, 'a')) obj = Puppet::Type.newfile( :path => path, :ensure => :directory, :backup => false, :recurse => true, :sourceselect => :first, :source => [one, two, three] ) catalog.add_resource obj catalog.apply File.should be_directory(path) File.should_not be_exist(File.join(path, 'a')) end it "should only recurse one level" do one = tmpdir('one') FileUtils.mkdir_p(File.join(one, 'a', 'b')) FileUtils.touch(File.join(one, 'a', 'b', 'c')) two = tmpdir('two') FileUtils.mkdir_p(File.join(two, 'z')) FileUtils.touch(File.join(two, 'z', 'y')) obj = Puppet::Type.newfile( :path => path, :ensure => :directory, :backup => false, :recurse => true, :recurselimit => 1, :sourceselect => :first, :source => [one, two] ) catalog.add_resource obj catalog.apply File.should be_exist(File.join(path, 'a')) File.should_not be_exist(File.join(path, 'a', 'b')) File.should_not be_exist(File.join(path, 'z')) end end describe "for a file" do it "should copy the first file that exists" do one = File.expand_path('thisdoesnotexist') two = tmpfile('two') File.open(two, "w") { |f| f.print 'yay' } three = tmpfile('three') File.open(three, "w") { |f| f.print 'no' } obj = Puppet::Type.newfile( :path => path, :ensure => :file, :backup => false, :sourceselect => :first, :source => [one, two, three] ) catalog.add_resource obj catalog.apply File.read(path).should == 'yay' end it "should copy an empty file" do one = File.expand_path('thisdoesnotexist') two = tmpfile('two') FileUtils.touch(two) three = tmpfile('three') File.open(three, "w") { |f| f.print 'no' } obj = Puppet::Type.newfile( :path => path, :ensure => :file, :backup => false, :sourceselect => :first, :source => [one, two, three] ) catalog.add_resource obj catalog.apply File.read(path).should == '' end end end describe "when sourceselect all" do describe "for a directory" do it "should recursively copy all sources from the first valid source" do one = tmpdir('one') two = tmpdir('two') three = tmpdir('three') four = tmpdir('four') [one, two, three, four].each {|dir| FileUtils.mkdir_p(dir)} File.open(File.join(one, 'a'), "w") { |f| f.print one } File.open(File.join(two, 'a'), "w") { |f| f.print two } File.open(File.join(two, 'b'), "w") { |f| f.print two } File.open(File.join(three, 'a'), "w") { |f| f.print three } File.open(File.join(three, 'c'), "w") { |f| f.print three } obj = Puppet::Type.newfile( :path => path, :ensure => :directory, :backup => false, :recurse => true, :sourceselect => :all, :source => [one, two, three, four] ) catalog.add_resource obj catalog.apply File.read(File.join(path, 'a')).should == one File.read(File.join(path, 'b')).should == two File.read(File.join(path, 'c')).should == three end it "should only recurse one level from each valid source" do one = tmpdir('one') FileUtils.mkdir_p(File.join(one, 'a', 'b')) FileUtils.touch(File.join(one, 'a', 'b', 'c')) two = tmpdir('two') FileUtils.mkdir_p(File.join(two, 'z')) FileUtils.touch(File.join(two, 'z', 'y')) obj = Puppet::Type.newfile( :path => path, :ensure => :directory, :backup => false, :recurse => true, :recurselimit => 1, :sourceselect => :all, :source => [one, two] ) catalog.add_resource obj catalog.apply File.should be_exist(File.join(path, 'a')) File.should_not be_exist(File.join(path, 'a', 'b')) File.should be_exist(File.join(path, 'z')) File.should_not be_exist(File.join(path, 'z', 'y')) end end end end end describe "when generating resources" do before do source = tmpfile("generating_in_catalog_source") Dir.mkdir(source) s1 = File.join(source, "one") s2 = File.join(source, "two") File.open(s1, "w") { |f| f.puts "uno" } File.open(s2, "w") { |f| f.puts "dos" } @file = described_class.new( :name => path, :source => source, :recurse => true, :backup => false ) catalog.add_resource @file end it "should add each generated resource to the catalog" do catalog.apply do |trans| catalog.resource(:file, File.join(path, "one")).must be_a(described_class) catalog.resource(:file, File.join(path, "two")).must be_a(described_class) end end it "should have an edge to each resource in the relationship graph" do catalog.apply do |trans| one = catalog.resource(:file, File.join(path, "one")) catalog.relationship_graph.should be_edge(@file, one) two = catalog.resource(:file, File.join(path, "two")) catalog.relationship_graph.should be_edge(@file, two) end end end describe "when copying files" do # Ticket #285. it "should be able to copy files with pound signs in their names" do source = tmpfile("filewith#signs") dest = tmpfile("destwith#signs") File.open(source, "w") { |f| f.print "foo" } file = described_class.new(:name => dest, :source => source) catalog.add_resource file catalog.apply File.read(dest).should == "foo" end it "should be able to copy files with spaces in their names" do source = tmpfile("filewith spaces") dest = tmpfile("destwith spaces") File.open(source, "w") { |f| f.print "foo" } File.chmod(0755, source) file = described_class.new(:path => dest, :source => source) catalog.add_resource file catalog.apply expected_mode = Puppet.features.microsoft_windows? ? 0644 : 0755 File.read(dest).should == "foo" (File.stat(dest).mode & 007777).should == expected_mode end it "should be able to copy individual files even if recurse has been specified" do source = tmpfile("source") dest = tmpfile("dest") File.open(source, "w") { |f| f.print "foo" } file = described_class.new(:name => dest, :source => source, :recurse => true) catalog.add_resource file catalog.apply File.read(dest).should == "foo" end end it "should create a file with content if ensure is omitted" do file = described_class.new( :path => path, :content => "this is some content, yo" ) catalog.add_resource file catalog.apply File.read(path).should == "this is some content, yo" end it "should create files with content if both content and ensure are set" do file = described_class.new( :path => path, :ensure => "file", :content => "this is some content, yo" ) catalog.add_resource file catalog.apply File.read(path).should == "this is some content, yo" end it "should delete files with sources but that are set for deletion" do source = tmpfile("source_source_with_ensure") File.open(source, "w") { |f| f.puts "yay" } File.open(path, "w") { |f| f.puts "boo" } file = described_class.new( :path => path, :ensure => :absent, :source => source, :backup => false ) catalog.add_resource file catalog.apply File.should_not be_exist(path) end describe "when sourcing" do let(:source) { source = tmpfile("source_default_values") File.open(source, "w") { |f| f.puts "yay" } source } it "should apply the source metadata values" do set_mode(0770, source) file = described_class.new( :path => path, :ensure => :file, :source => source, :backup => false ) catalog.add_resource file catalog.apply get_owner(path).should == get_owner(source) get_group(path).should == get_group(source) (get_mode(path) & 07777).should == 0770 end it "should override the default metadata values" do set_mode(0770, source) file = described_class.new( :path => path, :ensure => :file, :source => source, :backup => false, :mode => 0440 ) catalog.add_resource file catalog.apply (get_mode(path) & 07777).should == 0440 end describe "on Windows systems", :if => Puppet.features.microsoft_windows? do it "should provide valid default values when ACLs are not supported" do Puppet::Util::Windows::Security.stubs(:supports_acl?).with(source).returns false file = described_class.new( :path => path, :ensure => :file, :source => source, :backup => false ) catalog.add_resource file catalog.apply get_owner(path).should == 'S-1-5-32-544' get_group(path).should == 'S-1-0-0' get_mode(path).should == 0644 end end end describe "when purging files" do before do sourcedir = tmpfile("purge_source") destdir = tmpfile("purge_dest") Dir.mkdir(sourcedir) Dir.mkdir(destdir) sourcefile = File.join(sourcedir, "sourcefile") @copiedfile = File.join(destdir, "sourcefile") @localfile = File.join(destdir, "localfile") @purgee = File.join(destdir, "to_be_purged") File.open(@localfile, "w") { |f| f.print "oldtest" } File.open(sourcefile, "w") { |f| f.print "funtest" } # this file should get removed File.open(@purgee, "w") { |f| f.print "footest" } lfobj = Puppet::Type.newfile( :title => "localfile", :path => @localfile, :content => "rahtest", :ensure => :file, :backup => false ) destobj = Puppet::Type.newfile( :title => "destdir", :path => destdir, :source => sourcedir, :backup => false, :purge => true, :recurse => true ) catalog.add_resource lfobj, destobj catalog.apply end it "should still copy remote files" do File.read(@copiedfile).should == 'funtest' end it "should not purge managed, local files" do File.read(@localfile).should == 'rahtest' end it "should purge files that are neither remote nor otherwise managed" do FileTest.should_not be_exist(@purgee) end end end diff --git a/spec/shared_behaviours/path_parameters.rb b/spec/shared_behaviours/path_parameters.rb index 2d195a18e..d5f06875b 100755 --- a/spec/shared_behaviours/path_parameters.rb +++ b/spec/shared_behaviours/path_parameters.rb @@ -1,160 +1,160 @@ # In order to use this correctly you must define a method to get an instance # of the type being tested, so that this code can remain generic: # # it_should_behave_like "all path parameters", :path do # def instance(path) # Puppet::Type.type(:example).new( # :name => 'foo', :require => 'bar', :path_param => path # ) # end # # That method will be invoked for each test to create the instance that we # subsequently test through the system; you should ensure that the minimum of # possible attributes are set to keep the tests clean. # # You must also pass the symbolic name of the parameter being tested to the # block, and optionally can pass a hash of additional options to the block. # # The known options are: # :array :: boolean, does this support arrays of paths, default true. shared_examples_for "all pathname parameters with arrays" do |win32| path_types = { "unix absolute" => %q{/foo/bar}, "unix relative" => %q{foo/bar}, "win32 non-drive absolute" => %q{\foo\bar}, "win32 non-drive relative" => %q{foo\bar}, "win32 drive absolute" => %q{c:\foo\bar}, "win32 drive relative" => %q{c:foo\bar} } describe "when given an array of paths" do (1..path_types.length).each do |n| path_types.keys.combination(n) do |set| data = path_types.collect { |k, v| set.member?(k) ? v : nil } .compact has_relative = set.find { |k| k =~ /relative/ or k =~ /non-drive/ } has_windows = set.find { |k| k =~ /win32/ } has_unix = set.find { |k| k =~ /unix/ } if has_relative or (has_windows and !win32) or (has_unix and win32) reject = true else reject = false end it "should #{reject ? 'reject' : 'accept'} #{set.join(", ")}" do if reject then expect { instance(data) }. - should raise_error Puppet::Error, /fully qualified/ + to raise_error Puppet::Error, /fully qualified/ else instance = instance(data) instance[@param].should == data end end it "should #{reject ? 'reject' : 'accept'} #{set.join(", ")} doubled" do if reject then expect { instance(data + data) }. - should raise_error Puppet::Error, /fully qualified/ + to raise_error Puppet::Error, /fully qualified/ else instance = instance(data + data) instance[@param].should == (data + data) end end end end end end shared_examples_for "all path parameters" do |param, options| # Extract and process options to the block. options ||= {} array = options[:array].nil? ? true : options.delete(:array) if options.keys.length > 0 then fail "unknown options for 'all path parameters': " + options.keys.sort.join(', ') end def instance(path) fail "we didn't implement the 'instance(path)' method in the it_should_behave_like block" end ######################################################################## # The actual testing code... before :all do @param = param end describe "on a Unix-like platform it", :as_platform => :posix do if array then it_should_behave_like "all pathname parameters with arrays", false end it "should accept a fully qualified path" do path = File.join('', 'foo') instance = instance(path) instance[@param].should == path end it "should give a useful error when the path is not absolute" do path = 'foo' expect { instance(path) }. - should raise_error Puppet::Error, /fully qualified/ + to raise_error Puppet::Error, /fully qualified/ end { "Unix" => '/', "Win32" => '\\' }.each do |style, slash| %w{q Q a A z Z c C}.sort.each do |drive| it "should reject drive letter '#{drive}' with #{style} path separators" do path = "#{drive}:#{slash}Program Files" expect { instance(path) }. - should raise_error Puppet::Error, /fully qualified/ + to raise_error Puppet::Error, /fully qualified/ end end end end describe "on a Windows-like platform it", :as_platform => :windows do if array then it_should_behave_like "all pathname parameters with arrays", true end it "should reject a fully qualified unix path" do path = '/foo' expect { instance(path) }.to raise_error(Puppet::Error, /fully qualified/) end it "should give a useful error when the path is not absolute" do path = 'foo' expect { instance(path) }. - should raise_error Puppet::Error, /fully qualified/ + to raise_error Puppet::Error, /fully qualified/ end it "also accepts Unix style path separators" do path = 'C:/Program Files' instance = instance(path) instance[@param].should == path end { "Unix" => '/', "Win32" => '\\' }.each do |style, slash| %w{q Q a A z Z c C}.sort.each do |drive| it "should accept drive letter '#{drive}' with #{style} path separators " do path = "#{drive}:#{slash}Program Files" instance = instance(path) instance[@param].should == path end end end { "UNC paths" => %q{\\\\foo\bar}, "unparsed local paths" => %q{\\\\?\c:\foo}, "unparsed UNC paths" => %q{\\\\?\foo\bar} }.each do |name, path| it "should accept #{name} as absolute" do instance = instance(path) instance[@param].should == path end end end end diff --git a/spec/shared_behaviours/things_that_declare_options.rb b/spec/shared_behaviours/things_that_declare_options.rb index ecdbfcaea..017a5ed78 100755 --- a/spec/shared_behaviours/things_that_declare_options.rb +++ b/spec/shared_behaviours/things_that_declare_options.rb @@ -1,262 +1,262 @@ # encoding: UTF-8 shared_examples_for "things that declare options" do it "should support options without arguments" do thing = add_options_to { option "--bar" } thing.should be_option :bar end it "should support options with an empty block" do thing = add_options_to do option "--foo" do # this section deliberately left blank end end thing.should be thing.should be_option :foo end { "--foo=" => :foo }.each do |input, option| it "should accept #{name.inspect}" do thing = add_options_to { option input } thing.should be_option option end end it "should support option documentation" do text = "Sturm und Drang (German pronunciation: [ˈʃtʊʁm ʊnt ˈdʁaŋ]) …" thing = add_options_to do option "--foo" do description text summary text end end thing.get_option(:foo).description.should == text end it "should list all the options" do thing = add_options_to do option "--foo" option "--bar", '-b' option "-q", "--quux" option "-f" option "--baz" end thing.options.should == [:foo, :bar, :quux, :f, :baz] end it "should detect conflicts in long options" do expect { add_options_to do option "--foo" option "--foo" end - }.should raise_error ArgumentError, /Option foo conflicts with existing option foo/i + }.to raise_error ArgumentError, /Option foo conflicts with existing option foo/i end it "should detect conflicts in short options" do expect { add_options_to do option "-f" option "-f" end - }.should raise_error ArgumentError, /Option f conflicts with existing option f/ + }.to raise_error ArgumentError, /Option f conflicts with existing option f/ end ["-f", "--foo"].each do |option| ["", " FOO", "=FOO", " [FOO]", "=[FOO]"].each do |argument| input = option + argument it "should detect conflicts within a single option like #{input.inspect}" do expect { add_options_to do option input, input end - }.should raise_error ArgumentError, /duplicates existing alias/ + }.to raise_error ArgumentError, /duplicates existing alias/ end end end # Verify the range of interesting conflicts to check for ordering causing # the behaviour to change, or anything exciting like that. [ %w{--foo}, %w{-f}, %w{-f --foo}, %w{--baz -f}, %w{-f --baz}, %w{-b --foo}, %w{--foo -b} ].each do |conflict| base = %w{--foo -f} it "should detect conflicts between #{base.inspect} and #{conflict.inspect}" do expect { add_options_to do option *base option *conflict end - }.should raise_error ArgumentError, /conflicts with existing option/ + }.to raise_error ArgumentError, /conflicts with existing option/ end end it "should fail if we are not consistent about taking an argument" do expect { add_options_to do option "--foo=bar", "--bar" end }. - should raise_error ArgumentError, /inconsistent about taking an argument/ + to raise_error ArgumentError, /inconsistent about taking an argument/ end it "should not accept optional arguments" do expect do thing = add_options_to do option "--foo=[baz]", "--bar=[baz]" end [:foo, :bar].each do |name| thing.should be_option name end end.to raise_error(ArgumentError, /optional arguments are not supported/) end describe "#takes_argument?" do it "should detect an argument being absent" do thing = add_options_to do option "--foo" end thing.get_option(:foo).should_not be_takes_argument end ["=FOO", " FOO"].each do |input| it "should detect an argument given #{input.inspect}" do thing = add_options_to do option "--foo#{input}" end thing.get_option(:foo).should be_takes_argument end end end describe "#optional_argument?" do it "should be false if no argument is present" do option = add_options_to do option "--foo" end.get_option(:foo) option.should_not be_takes_argument option.should_not be_optional_argument end ["=FOO", " FOO"].each do |input| it "should be false if the argument is mandatory (like #{input.inspect})" do option = add_options_to do option "--foo#{input}" end.get_option(:foo) option.should be_takes_argument option.should_not be_optional_argument end end ["=[FOO]", " [FOO]"].each do |input| it "should fail if the argument is optional (like #{input.inspect})" do expect do option = add_options_to do option "--foo#{input}" end.get_option(:foo) option.should be_takes_argument option.should be_optional_argument end.to raise_error(ArgumentError, /optional arguments are not supported/) end end end describe "#default_to" do it "should not have a default value by default" do option = add_options_to do option "--foo" end.get_option(:foo) option.should_not be_has_default end it "should accept a block for the default value" do option = add_options_to do option "--foo" do default_to do 12 end end end.get_option(:foo) option.should be_has_default end it "should invoke the block when asked for the default value" do invoked = false option = add_options_to do option "--foo" do default_to do invoked = true end end end.get_option(:foo) option.should be_has_default option.default.should be_true invoked.should be_true end it "should return the value of the block when asked for the default" do option = add_options_to do option "--foo" do default_to do 12 end end end.get_option(:foo) option.should be_has_default option.default.should == 12 end it "should invoke the block every time the default is requested" do option = add_options_to do option "--foo" do default_to do {} end end end.get_option(:foo) first = option.default.object_id second = option.default.object_id third = option.default.object_id first.should_not == second first.should_not == third second.should_not == third end it "should fail if the option has a default and is required" do expect { add_options_to do option "--foo" do required default_to do 12 end end end }.to raise_error ArgumentError, /can't be optional and have a default value/ expect { add_options_to do option "--foo" do default_to do 12 end required end end }.to raise_error ArgumentError, /can't be optional and have a default value/ end it "should fail if default_to has no block" do expect { add_options_to do option "--foo" do default_to end end }. to raise_error ArgumentError, /default_to requires a block/ end it "should fail if default_to is invoked twice" do expect { add_options_to do option "--foo" do default_to do 12 end default_to do "fun" end end end }.to raise_error ArgumentError, /already has a default value/ end [ "one", "one, two", "one, *two" ].each do |input| it "should fail if the block has the wrong arity (#{input})" do expect { add_options_to do option "--foo" do eval "default_to do |#{input}| 12 end" end end }.to raise_error ArgumentError, /should not take any arguments/ end end end end diff --git a/spec/unit/application/facts_spec.rb b/spec/unit/application/facts_spec.rb index 7a7c36597..4554f4273 100755 --- a/spec/unit/application/facts_spec.rb +++ b/spec/unit/application/facts_spec.rb @@ -1,28 +1,28 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/application/facts' describe Puppet::Application::Facts do before :each do subject.command_line.stubs(:subcommand_name).returns 'facts' end it "should fail if no key is given to find" do subject.command_line.stubs(:args).returns %w{find} expect { expect { subject.run }.to exit_with 1 }.to have_printed /err: puppet facts find takes 1 argument, but you gave 0/ @logs.first.to_s.should =~ /puppet facts find takes 1 argument, but you gave 0/ end it "should return facts if a key is given to find", :'fails_on_ruby_1.9.2' => true do Puppet::Node::Facts.indirection.reset_terminus_class subject.command_line.stubs(:args).returns %w{find whatever --render-as yaml} expect { expect { subject.run }.to exit_with 0 - }.should have_printed(/object:Puppet::Node::Facts/) + }.to have_printed(/object:Puppet::Node::Facts/) @logs.should be_empty end end diff --git a/spec/unit/application_spec.rb b/spec/unit/application_spec.rb index b26e8e0b2..ef94ff7d0 100755 --- a/spec/unit/application_spec.rb +++ b/spec/unit/application_spec.rb @@ -1,618 +1,618 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/application' require 'puppet' require 'getoptlong' describe Puppet::Application do before do Puppet::Util::Instrumentation.stubs(:init) @app = Class.new(Puppet::Application).new @appclass = @app.class @app.stubs(:name).returns("test_app") # avoid actually trying to parse any settings Puppet.settings.stubs(:parse) end describe "finding" do before do @klass = Puppet::Application @klass.stubs(:puts) end it "should find classes in the namespace" do @klass.find("Agent").should == @klass::Agent end it "should not find classes outside the namespace", :'fails_on_ruby_1.9.2' => true do expect { @klass.find("String") }.to exit_with 1 end it "should exit if it can't find a class" do reg = "Unable to find application 'ThisShallNeverEverEverExist'. " reg += "no such file to load -- puppet/application/thisshallneverevereverexist" @klass.expects(:puts).with(reg) expect { @klass.find("ThisShallNeverEverEverExist") }.to exit_with 1 end it "#12114: should prevent File namespace collisions" do # have to require the file face once, then the second time around it would fail @klass.find("File").should == Puppet::Application::File @klass.find("File").should == Puppet::Application::File end end describe ".run_mode" do it "should default to user" do @appclass.run_mode.name.should == :user end it "should set and get a value" do @appclass.run_mode :agent @appclass.run_mode.name.should == :agent end end it "should sadly and frighteningly allow run_mode to change at runtime" do class TestApp < Puppet::Application run_mode :master def run_command # This is equivalent to calling these methods externally to the # instance, but since this is what "real world" code is likely to do # (and we need the class anyway) we may as well test that. --daniel 2011-02-03 set_run_mode self.class.run_mode "agent" end end Puppet.features.stubs(:syslog?).returns(true) Puppet[:run_mode].should == "user" expect { app = TestApp.new Puppet[:run_mode].should == "master" app.run app.class.run_mode.name.should == :agent $puppet_application_mode.name.should == :agent - }.should_not raise_error + }.to_not raise_error Puppet[:run_mode].should == "agent" end it "it should not allow run mode to be set multiple times" do pending "great floods of tears, you can do this right now" # --daniel 2011-02-03 app = Puppet::Application.new expect { app.set_run_mode app.class.run_mode "master" $puppet_application_mode.name.should == :master app.set_run_mode app.class.run_mode "agent" $puppet_application_mode.name.should == :agent - }.should raise_error + }.to raise_error end it "should explode when an invalid run mode is set at runtime, for great victory" # ...but you can, and while it will explode, that only happens too late for # us to easily test. --daniel 2011-02-03 it "should have a run entry-point" do @app.should respond_to(:run) end it "should have a read accessor to options" do @app.should respond_to(:options) end it "should include a default setup method" do @app.should respond_to(:setup) end it "should include a default preinit method" do @app.should respond_to(:preinit) end it "should include a default run_command method" do @app.should respond_to(:run_command) end it "should invoke main as the default" do @app.expects( :main ) @app.run_command end it "should initialize the Puppet Instrumentation layer on creation" do Puppet::Util::Instrumentation.expects(:init) Class.new(Puppet::Application).new end describe 'when invoking clear!' do before :each do Puppet::Application.run_status = :stop_requested Puppet::Application.clear! end it 'should have nil run_status' do Puppet::Application.run_status.should be_nil end it 'should return false for restart_requested?' do Puppet::Application.restart_requested?.should be_false end it 'should return false for stop_requested?' do Puppet::Application.stop_requested?.should be_false end it 'should return false for interrupted?' do Puppet::Application.interrupted?.should be_false end it 'should return true for clear?' do Puppet::Application.clear?.should be_true end end describe 'after invoking stop!' do before :each do Puppet::Application.run_status = nil Puppet::Application.stop! end after :each do Puppet::Application.run_status = nil end it 'should have run_status of :stop_requested' do Puppet::Application.run_status.should == :stop_requested end it 'should return true for stop_requested?' do Puppet::Application.stop_requested?.should be_true end it 'should return false for restart_requested?' do Puppet::Application.restart_requested?.should be_false end it 'should return true for interrupted?' do Puppet::Application.interrupted?.should be_true end it 'should return false for clear?' do Puppet::Application.clear?.should be_false end end describe 'when invoking restart!' do before :each do Puppet::Application.run_status = nil Puppet::Application.restart! end after :each do Puppet::Application.run_status = nil end it 'should have run_status of :restart_requested' do Puppet::Application.run_status.should == :restart_requested end it 'should return true for restart_requested?' do Puppet::Application.restart_requested?.should be_true end it 'should return false for stop_requested?' do Puppet::Application.stop_requested?.should be_false end it 'should return true for interrupted?' do Puppet::Application.interrupted?.should be_true end it 'should return false for clear?' do Puppet::Application.clear?.should be_false end end describe 'when performing a controlled_run' do it 'should not execute block if not :clear?' do Puppet::Application.run_status = :stop_requested target = mock 'target' target.expects(:some_method).never Puppet::Application.controlled_run do target.some_method end end it 'should execute block if :clear?' do Puppet::Application.run_status = nil target = mock 'target' target.expects(:some_method).once Puppet::Application.controlled_run do target.some_method end end describe 'on POSIX systems', :if => Puppet.features.posix? do it 'should signal process with HUP after block if restart requested during block execution', :'fails_on_ruby_1.9.2' => true do Puppet::Application.run_status = nil target = mock 'target' target.expects(:some_method).once old_handler = trap('HUP') { target.some_method } begin Puppet::Application.controlled_run do Puppet::Application.run_status = :restart_requested end ensure trap('HUP', old_handler) end end end after :each do Puppet::Application.run_status = nil end end describe "when parsing command-line options" do before :each do @app.command_line.stubs(:args).returns([]) Puppet.settings.stubs(:optparse_addargs).returns([]) end it "should pass the banner to the option parser" do option_parser = stub "option parser" option_parser.stubs(:on) option_parser.stubs(:parse!) @app.class.instance_eval do banner "banner" end OptionParser.expects(:new).with("banner").returns(option_parser) @app.parse_options end it "should get options from Puppet.settings.optparse_addargs" do Puppet.settings.expects(:optparse_addargs).returns([]) @app.parse_options end it "should add Puppet.settings options to OptionParser" do Puppet.settings.stubs(:optparse_addargs).returns( [["--option","-o", "Funny Option"]]) Puppet.settings.expects(:handlearg).with("--option", 'true') @app.command_line.stubs(:args).returns(["--option"]) @app.parse_options end it "should ask OptionParser to parse the command-line argument" do @app.command_line.stubs(:args).returns(%w{ fake args }) OptionParser.any_instance.expects(:parse!).with(%w{ fake args }) @app.parse_options end describe "when using --help" do it "should call exit" do @app.stubs(:puts) expect { @app.handle_help(nil) }.to exit_with 0 end end describe "when using --version" do it "should declare a version option" do @app.should respond_to(:handle_version) end it "should exit after printing the version" do @app.stubs(:puts) expect { @app.handle_version(nil) }.to exit_with 0 end end describe "when dealing with an argument not declared directly by the application" do it "should pass it to handle_unknown if this method exists" do Puppet.settings.stubs(:optparse_addargs).returns([["--not-handled", :REQUIRED]]) @app.expects(:handle_unknown).with("--not-handled", "value").returns(true) @app.command_line.stubs(:args).returns(["--not-handled", "value"]) @app.parse_options end it "should pass it to Puppet.settings if handle_unknown says so" do Puppet.settings.stubs(:optparse_addargs).returns([["--topuppet", :REQUIRED]]) @app.stubs(:handle_unknown).with("--topuppet", "value").returns(false) Puppet.settings.expects(:handlearg).with("--topuppet", "value") @app.command_line.stubs(:args).returns(["--topuppet", "value"]) @app.parse_options end it "should pass it to Puppet.settings if there is no handle_unknown method" do Puppet.settings.stubs(:optparse_addargs).returns([["--topuppet", :REQUIRED]]) @app.stubs(:respond_to?).returns(false) Puppet.settings.expects(:handlearg).with("--topuppet", "value") @app.command_line.stubs(:args).returns(["--topuppet", "value"]) @app.parse_options end it "should transform boolean false value to string for Puppet.settings" do Puppet.settings.expects(:handlearg).with("--option", "false") @app.handlearg("--option", false) end it "should transform boolean true value to string for Puppet.settings" do Puppet.settings.expects(:handlearg).with("--option", "true") @app.handlearg("--option", true) end it "should transform boolean option to normal form for Puppet.settings" do Puppet.settings.expects(:handlearg).with("--option", "true") @app.handlearg("--[no-]option", true) end it "should transform boolean option to no- form for Puppet.settings" do Puppet.settings.expects(:handlearg).with("--no-option", "false") @app.handlearg("--[no-]option", false) end end end describe "when calling default setup" do before :each do @app.stubs(:should_parse_config?).returns(false) @app.options.stubs(:[]) end [ :debug, :verbose ].each do |level| it "should honor option #{level}" do @app.options.stubs(:[]).with(level).returns(true) Puppet::Util::Log.stubs(:newdestination) @app.setup Puppet::Util::Log.level.should == (level == :verbose ? :info : :debug) end end it "should honor setdest option" do @app.options.stubs(:[]).with(:setdest).returns(false) Puppet::Util::Log.expects(:setup_default) @app.setup end end describe "when configuring routes" do include PuppetSpec::Files before :each do Puppet::Node.indirection.reset_terminus_class end after :each do Puppet::Node.indirection.reset_terminus_class end it "should use the routes specified for only the active application" do Puppet[:route_file] = tmpfile('routes') File.open(Puppet[:route_file], 'w') do |f| f.print <<-ROUTES test_app: node: terminus: exec other_app: node: terminus: plain catalog: terminus: invalid ROUTES end @app.configure_indirector_routes Puppet::Node.indirection.terminus_class.should == 'exec' end it "should not fail if the route file doesn't exist" do Puppet[:route_file] = "/dev/null/non-existent" - expect { @app.configure_indirector_routes }.should_not raise_error + expect { @app.configure_indirector_routes }.to_not raise_error end it "should raise an error if the routes file is invalid" do Puppet[:route_file] = tmpfile('routes') File.open(Puppet[:route_file], 'w') do |f| f.print <<-ROUTES invalid : : yaml ROUTES end - expect { @app.configure_indirector_routes }.should raise_error + expect { @app.configure_indirector_routes }.to raise_error end end describe "when running" do before :each do @app.stubs(:preinit) @app.stubs(:setup) @app.stubs(:parse_options) end it "should call preinit" do @app.stubs(:run_command) @app.expects(:preinit) @app.run end it "should call parse_options" do @app.stubs(:run_command) @app.expects(:parse_options) @app.run end it "should call run_command" do @app.expects(:run_command) @app.run end it "should parse Puppet configuration if should_parse_config is called" do @app.stubs(:run_command) @app.class.should_parse_config Puppet.settings.expects(:parse) @app.run end it "should not parse_option if should_not_parse_config is called" do @app.stubs(:run_command) @app.class.should_not_parse_config Puppet.settings.expects(:parse).never @app.run end it "should parse Puppet configuration if needed" do @app.stubs(:run_command) @app.stubs(:should_parse_config?).returns(true) Puppet.settings.expects(:parse) @app.run end it "should call run_command" do @app.expects(:run_command) @app.run end it "should call main as the default command" do @app.expects(:main) @app.run end it "should warn and exit if no command can be called" do $stderr.expects(:puts) expect { @app.run }.to exit_with 1 end it "should raise an error if dispatch returns no command" do @app.stubs(:get_command).returns(nil) $stderr.expects(:puts) expect { @app.run }.to exit_with 1 end it "should raise an error if dispatch returns an invalid command" do @app.stubs(:get_command).returns(:this_function_doesnt_exist) $stderr.expects(:puts) expect { @app.run }.to exit_with 1 end end describe "when metaprogramming" do describe "when calling option" do it "should create a new method named after the option" do @app.class.option("--test1","-t") do end @app.should respond_to(:handle_test1) end it "should transpose in option name any '-' into '_'" do @app.class.option("--test-dashes-again","-t") do end @app.should respond_to(:handle_test_dashes_again) end it "should create a new method called handle_test2 with option(\"--[no-]test2\")" do @app.class.option("--[no-]test2","-t") do end @app.should respond_to(:handle_test2) end describe "when a block is passed" do it "should create a new method with it" do @app.class.option("--[no-]test2","-t") do raise "I can't believe it, it works!" end - lambda { @app.handle_test2 }.should raise_error + expect { @app.handle_test2 }.to raise_error end it "should declare the option to OptionParser" do OptionParser.any_instance.stubs(:on) OptionParser.any_instance.expects(:on).with { |*arg| arg[0] == "--[no-]test3" } @app.class.option("--[no-]test3","-t") do end @app.parse_options end it "should pass a block that calls our defined method" do OptionParser.any_instance.stubs(:on) OptionParser.any_instance.stubs(:on).with('--test4','-t').yields(nil) @app.expects(:send).with(:handle_test4, nil) @app.class.option("--test4","-t") do end @app.parse_options end end describe "when no block is given" do it "should declare the option to OptionParser" do OptionParser.any_instance.stubs(:on) OptionParser.any_instance.expects(:on).with("--test4","-t") @app.class.option("--test4","-t") @app.parse_options end it "should give to OptionParser a block that adds the the value to the options array" do OptionParser.any_instance.stubs(:on) OptionParser.any_instance.stubs(:on).with("--test4","-t").yields(nil) @app.options.expects(:[]=).with(:test4,nil) @app.class.option("--test4","-t") @app.parse_options end end end end end diff --git a/spec/unit/face/ca_spec.rb b/spec/unit/face/ca_spec.rb index f489fbcb7..2382c151a 100755 --- a/spec/unit/face/ca_spec.rb +++ b/spec/unit/face/ca_spec.rb @@ -1,400 +1,400 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/face' describe Puppet::Face[:ca, '0.1.0'], :unless => Puppet.features.microsoft_windows? do include PuppetSpec::Files before :each do Puppet.run_mode.stubs(:master?).returns(true) Puppet[:ca] = true Puppet[:ssldir] = tmpdir("face-ca-ssldir") Puppet::SSL::Host.ca_location = :only Puppet[:certificate_revocation] = true # This is way more intimate than I want to be with the implementation, but # there doesn't seem any other way to test this. --daniel 2011-07-18 Puppet::SSL::CertificateAuthority.stubs(:instance).returns( # ...and this actually does the directory creation, etc. Puppet::SSL::CertificateAuthority.new ) end def make_certs(csr_names, crt_names) Array(csr_names).map do |name| Puppet::SSL::Host.new(name).generate_certificate_request end Array(crt_names).map do |name| Puppet::SSL::Host.new(name).generate end end context "#verify" do let :action do Puppet::Face[:ca, '0.1.0'].get_action(:verify) end it "should not explode if there is no certificate" do expect { subject.verify('random-host').should == { :host => 'random-host', :valid => false, :error => 'Could not find a certificate for random-host' } - }.should_not raise_error + }.to_not raise_error end it "should not explode if there is only a CSR" do make_certs('random-host', []) expect { subject.verify('random-host').should == { :host => 'random-host', :valid => false, :error => 'Could not find a certificate for random-host' } - }.should_not raise_error + }.to_not raise_error end it "should verify a signed certificate" do make_certs([], 'random-host') subject.verify('random-host').should == { :host => 'random-host', :valid => true } end it "should not verify a revoked certificate" do make_certs([], 'random-host') subject.revoke('random-host') expect { subject.verify('random-host').should == { :host => 'random-host', :valid => false, :error => 'certificate revoked' } - }.should_not raise_error + }.to_not raise_error end it "should verify a revoked certificate if CRL use was turned off" do make_certs([], 'random-host') subject.revoke('random-host') Puppet[:certificate_revocation] = false subject.verify('random-host').should == { :host => 'random-host', :valid => true } end end context "#fingerprint" do let :action do Puppet::Face[:ca, '0.1.0'].get_action(:fingerprint) end it "should have a 'digest' option" do action.should be_option :digest end it "should not explode if there is no certificate" do expect { subject.fingerprint('random-host').should be_nil - }.should_not raise_error + }.to_not raise_error end it "should fingerprint a CSR" do make_certs('random-host', []) expect { subject.fingerprint('random-host').should =~ /^[0-9A-F:]+$/ - }.should_not raise_error + }.to_not raise_error end it "should fingerprint a certificate" do make_certs([], 'random-host') subject.fingerprint('random-host').should =~ /^[0-9A-F:]+$/ end %w{md5 MD5 sha1 ShA1 SHA1 RIPEMD160 sha256 sha512}.each do |digest| it "should fingerprint with #{digest.inspect}" do make_certs([], 'random-host') subject.fingerprint('random-host', :digest => digest).should =~ /^[0-9A-F:]+$/ end it "should fingerprint with #{digest.to_sym} as a symbol" do make_certs([], 'random-host') subject.fingerprint('random-host', :digest => digest.to_sym). should =~ /^[0-9A-F:]+$/ end end end context "#print" do let :action do Puppet::Face[:ca, '0.1.0'].get_action(:print) end it "should not explode if there is no certificate" do expect { subject.print('random-host').should be_nil - }.should_not raise_error + }.to_not raise_error end it "should return nothing if there is only a CSR" do make_certs('random-host', []) expect { subject.print('random-host').should be_nil - }.should_not raise_error + }.to_not raise_error end it "should return the certificate content if there is a cert" do make_certs([], 'random-host') text = subject.print('random-host') text.should be_an_instance_of String text.should =~ /^Certificate:/ text.should =~ /Issuer: CN=Puppet CA: / text.should =~ /Subject: CN=random-host$/ end end context "#sign" do let :action do Puppet::Face[:ca, '0.1.0'].get_action(:sign) end it "should not explode if there is no CSR" do expect { subject.sign('random-host'). should == 'Could not find certificate request for random-host' - }.should_not raise_error + }.to_not raise_error end it "should not explode if there is a signed cert" do make_certs([], 'random-host') expect { subject.sign('random-host'). should == 'Could not find certificate request for random-host' - }.should_not raise_error + }.to_not raise_error end it "should sign a CSR if one exists" do make_certs('random-host', []) subject.sign('random-host').should be_an_instance_of Puppet::SSL::Certificate list = subject.list(:signed => true) list.length.should == 1 list.first.name.should == 'random-host' end describe "when the CSR specifies DNS alt names" do let(:host) { Puppet::SSL::Host.new('someone') } before :each do host.generate_certificate_request(:dns_alt_names => 'some,alt,names') end it "should sign the CSR if DNS alt names are allowed" do subject.sign('someone', :allow_dns_alt_names => true) host.certificate.should be_a(Puppet::SSL::Certificate) end it "should refuse to sign the CSR if DNS alt names are not allowed" do certname = 'someone' expect do subject.sign(certname) end.to raise_error(Puppet::SSL::CertificateAuthority::CertificateSigningError, /CSR '#{certname}' contains subject alternative names \(.*\), which are disallowed. Use `puppet cert --allow-dns-alt-names sign #{certname}` to sign this request./i) host.certificate.should be_nil end end end context "#generate" do let :action do Puppet::Face[:ca, '0.1.0'].get_action(:generate) end it "should generate a certificate if requested" do subject.list(:all => true).should == [] subject.generate('random-host') list = subject.list(:signed => true) list.length.should == 1 list.first.name.should == 'random-host' end it "should not explode if a CSR with that name already exists" do make_certs('random-host', []) expect { subject.generate('random-host').should =~ /already has a certificate request/ - }.should_not raise_error + }.to_not raise_error end it "should not explode if the certificate with that name already exists" do make_certs([], 'random-host') expect { subject.generate('random-host').should =~ /already has a certificate/ - }.should_not raise_error + }.to_not raise_error end it "should include the specified DNS alt names" do subject.generate('some-host', :dns_alt_names => 'some,alt,names') host = subject.list(:signed => true).first host.name.should == 'some-host' host.certificate.subject_alt_names.should =~ %w[DNS:some DNS:alt DNS:names DNS:some-host] subject.list(:pending => true).should be_empty end end context "#revoke" do let :action do Puppet::Face[:ca, '0.1.0'].get_action(:revoke) end it "should not explode when asked to revoke something that doesn't exist" do - expect { subject.revoke('nonesuch') }.should_not raise_error + expect { subject.revoke('nonesuch') }.to_not raise_error end it "should let the user know what went wrong" do subject.revoke('nonesuch').should == 'Nothing was revoked' end it "should revoke a certificate" do make_certs([], 'random-host') found = subject.list(:all => true, :subject => 'random-host') subject.get_action(:list).when_rendering(:console).call(found). should =~ /^\+ random-host/ subject.revoke('random-host') found = subject.list(:all => true, :subject => 'random-host') subject.get_action(:list).when_rendering(:console).call(found). should =~ /^- random-host \([:0-9A-F]+\) \(certificate revoked\)/ end end context "#destroy" do let :action do Puppet::Face[:ca, '0.1.0'].get_action(:destroy) end it "should not explode when asked to delete something that doesn't exist" do - expect { subject.destroy('nonesuch') }.should_not raise_error + expect { subject.destroy('nonesuch') }.to_not raise_error end it "should let the user know if nothing was deleted" do subject.destroy('nonesuch').should == "Nothing was deleted" end it "should destroy a CSR, if we have one" do make_certs('random-host', []) subject.list(:pending => true, :subject => 'random-host').should_not == [] subject.destroy('random-host') subject.list(:pending => true, :subject => 'random-host').should == [] end it "should destroy a certificate, if we have one" do make_certs([], 'random-host') subject.list(:signed => true, :subject => 'random-host').should_not == [] subject.destroy('random-host') subject.list(:signed => true, :subject => 'random-host').should == [] end it "should tell the user something was deleted" do make_certs([], 'random-host') subject.list(:signed => true, :subject => 'random-host').should_not == [] subject.destroy('random-host'). should == "Deleted for random-host: Puppet::SSL::Certificate, Puppet::SSL::Key" end end context "#list" do let :action do Puppet::Face[:ca, '0.1.0'].get_action(:list) end context "options" do subject { Puppet::Face[:ca, '0.1.0'].get_action(:list) } it { should be_option :pending } it { should be_option :signed } it { should be_option :all } it { should be_option :subject } end context "with no hosts in CA" do [ {}, { :pending => true }, { :signed => true }, { :all => true }, ].each do |type| it "should return nothing for #{type.inspect}" do subject.list(type).should == [] end it "should not fail when a matcher is passed" do expect { subject.list(type.merge :subject => '.').should == [] - }.should_not raise_error + }.to_not raise_error end context "when_rendering :console" do it "should return nothing for #{type.inspect}" do action.when_rendering(:console).call(subject.list(type)).should == "" end end end end context "with some hosts" do csr_names = (1..3).map {|n| "csr-#{n}" } crt_names = (1..3).map {|n| "crt-#{n}" } all_names = csr_names + crt_names { {} => csr_names, { :pending => true } => csr_names, { :signed => true } => crt_names, { :all => true } => all_names, { :pending => true, :signed => true } => all_names, }.each do |input, expect| it "should map #{input.inspect} to #{expect.inspect}" do make_certs(csr_names, crt_names) subject.list(input).map(&:name).should =~ expect end ['', '.', '2', 'none'].each do |pattern| filtered = expect.select {|x| Regexp.new(pattern).match(x) } it "should filter all hosts matching #{pattern.inspect} to #{filtered.inspect}" do make_certs(csr_names, crt_names) subject.list(input.merge :subject => pattern).map(&:name).should =~ filtered end end end context "when_rendering :console" do { [["csr1.local"], []] => '^ csr1.local ', [[], ["crt1.local"]] => '^\+ crt1.local ', [["csr2"], ["crt2"]] => ['^ csr2 ', '^\+ crt2 '] }.each do |input, pattern| it "should render #{input.inspect} to match #{pattern.inspect}" do make_certs(*input) text = action.when_rendering(:console).call(subject.list(:all => true)) Array(pattern).each do |item| text.should =~ Regexp.new(item) end end end end end end actions = %w{destroy list revoke generate sign print verify fingerprint} actions.each do |action| it { should be_action action } it "should fail #{action} when not a CA" do Puppet[:ca] = false expect { case subject.method(action).arity when -1 then subject.send(action) when -2 then subject.send(action, 'dummy') else raise "#{action} has arity #{subject.method(action).arity}" end - }.should raise_error(/Not a CA/) + }.to raise_error(/Not a CA/) end end end diff --git a/spec/unit/face/help_spec.rb b/spec/unit/face/help_spec.rb index ef66660ff..b4c74f4d3 100755 --- a/spec/unit/face/help_spec.rb +++ b/spec/unit/face/help_spec.rb @@ -1,129 +1,129 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/face' describe Puppet::Face[:help, '0.0.1'] do it "should have a help action" do subject.should be_action :help end it "should have a default action of help" do subject.get_action('help').should be_default end it "should accept a call with no arguments" do - expect { subject.help() }.should_not raise_error + expect { subject.help() }.to_not raise_error end it "should accept a face name" do - expect { subject.help(:help) }.should_not raise_error + expect { subject.help(:help) }.to_not raise_error end it "should accept a face and action name" do - expect { subject.help(:help, :help) }.should_not raise_error + expect { subject.help(:help, :help) }.to_not raise_error end it "should fail if more than a face and action are given" do expect { subject.help(:help, :help, :for_the_love_of_god) }. - should raise_error ArgumentError + to raise_error ArgumentError end it "should treat :current and 'current' identically" do subject.help(:help, :version => :current).should == subject.help(:help, :version => 'current') end it "should complain when the request version of a face is missing" do expect { subject.help(:huzzah, :bar, :version => '17.0.0') }. - should raise_error Puppet::Error + to raise_error Puppet::Error end it "should find a face by version" do face = Puppet::Face[:huzzah, :current] subject.help(:huzzah, :version => face.version). should == subject.help(:huzzah, :version => :current) end context "when listing subcommands" do subject { Puppet::Face[:help, :current].help } RSpec::Matchers.define :have_a_summary do match do |instance| instance.summary.is_a?(String) end end # Check a precondition for the next block; if this fails you have # something odd in your set of face, and we skip testing things that # matter. --daniel 2011-04-10 it "should have at least one face with a summary" do Puppet::Face.faces.should be_any do |name| Puppet::Face[name, :current].summary end end it "should list all faces" do Puppet::Face.faces.each do |name| face = Puppet::Face[name, :current] summary = face.summary subject.should =~ %r{ #{name} } summary and subject.should =~ %r{ #{name} +#{summary}} end end Puppet::Face.faces.each do |name| it "should have a summary for #{name}" do Puppet::Face[name, :current].should have_a_summary end end it "should list all legacy applications" do Puppet::Face[:help, :current].legacy_applications.each do |appname| subject.should =~ %r{ #{appname} } summary = Puppet::Face[:help, :current].horribly_extract_summary_from(appname) summary and subject.should =~ %r{ #{summary}\b} end end end context "#legacy_applications" do subject { Puppet::Face[:help, :current].legacy_applications } # If we don't, these tests are ... less than useful, because they assume # it. When this breaks you should consider ditching the entire feature # and tests, but if not work out how to fake one. --daniel 2011-04-11 it { should have_at_least(1).item } # Meh. This is nasty, but we can't control the other list; the specific # bug that caused these to be listed is annoyingly subtle and has a nasty # fix, so better to have a "fail if you do something daft" trigger in # place here, I think. --daniel 2011-04-11 %w{face_base indirection_base}.each do |name| it { should_not include name } end end context "help for legacy applications" do subject { Puppet::Face[:help, :current] } let :appname do subject.legacy_applications.first end # This test is purposely generic, so that as we eliminate legacy commands # we don't get into a loop where we either test a face-based replacement # and fail to notice breakage, or where we have to constantly rewrite this # test and all. --daniel 2011-04-11 it "should return the legacy help when given the subcommand" do help = subject.help(appname) help.should =~ /puppet-#{appname}/ %w{SYNOPSIS USAGE DESCRIPTION OPTIONS COPYRIGHT}.each do |heading| help.should =~ /^#{heading}$/ end end it "should fail when asked for an action on a legacy command" do expect { subject.help(appname, :whatever) }. to raise_error ArgumentError, /Legacy subcommands don't take actions/ end end end diff --git a/spec/unit/face/node_spec.rb b/spec/unit/face/node_spec.rb index b126af6bb..d899c5a61 100755 --- a/spec/unit/face/node_spec.rb +++ b/spec/unit/face/node_spec.rb @@ -1,271 +1,272 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/face' describe Puppet::Face[:node, '0.0.1'] do after :all do Puppet::SSL::Host.ca_location = :none end describe '#cleanup' do it "should clean everything" do { "cert" => ['hostname'], "cached_facts" => ['hostname'], "cached_node" => ['hostname'], "reports" => ['hostname'], "storeconfigs" => ['hostname', :unexport] }.each { |k, v| subject.expects("clean_#{k}".to_sym).with(*v) } subject.cleanup('hostname', :unexport) end end describe 'when running #clean' do before :each do Puppet::Node::Facts.indirection.stubs(:terminus_class=) Puppet::Node::Facts.indirection.stubs(:cache_class=) Puppet::Node.stubs(:terminus_class=) Puppet::Node.stubs(:cache_class=) end it 'should invoke #cleanup' do subject.expects(:cleanup).with('hostname', nil) subject.clean('hostname') end end describe "clean action" do before :each do Puppet::Node::Facts.indirection.stubs(:terminus_class=) Puppet::Node::Facts.indirection.stubs(:cache_class=) Puppet::Node.stubs(:terminus_class=) Puppet::Node.stubs(:cache_class=) subject.stubs(:cleanup) end it "should have a clean action" do subject.should be_action :clean end it "should not accept a call with no arguments" do - expect { subject.clean() }.should raise_error + expect { subject.clean() }.to raise_error end it "should accept a node name" do - expect { subject.clean('hostname') }.should_not raise_error + expect { subject.clean('hostname') }.to_not raise_error end it "should accept more than one node name" do expect do subject.clean('hostname', 'hostname2', {}) - end.should_not raise_error + end.to_not raise_error expect do subject.clean('hostname', 'hostname2', 'hostname3', { :unexport => true }) - end.should_not raise_error + end.to_not raise_error end it "should accept the option --unexport" do - expect { subject.help('hostname', :unexport => true) }. - should_not raise_error ArgumentError + expect { + subject.help('hostname', :unexport => true) + }.to_not raise_error(ArgumentError) end context "clean action" do subject { Puppet::Face[:node, :current] } before :each do Puppet::Util::Log.stubs(:newdestination) Puppet::Util::Log.stubs(:level=) end describe "during setup" do it "should set facts terminus and cache class to yaml" do Puppet::Node::Facts.indirection.expects(:terminus_class=).with(:yaml) Puppet::Node::Facts.indirection.expects(:cache_class=).with(:yaml) subject.clean('hostname') end it "should run in master mode" do subject.clean('hostname') $puppet_application_mode.name.should == :master end it "should set node cache as yaml" do Puppet::Node.indirection.expects(:terminus_class=).with(:yaml) Puppet::Node.indirection.expects(:cache_class=).with(:yaml) subject.clean('hostname') end it "should manage the certs if the host is a CA" do Puppet::SSL::CertificateAuthority.stubs(:ca?).returns(true) Puppet::SSL::Host.expects(:ca_location=).with(:local) subject.clean('hostname') end it "should not manage the certs if the host is not a CA" do Puppet::SSL::CertificateAuthority.stubs(:ca?).returns(false) Puppet::SSL::Host.expects(:ca_location=).with(:none) subject.clean('hostname') end end describe "when cleaning certificate" do before :each do Puppet::SSL::Host.stubs(:destroy) @ca = mock() Puppet::SSL::CertificateAuthority.stubs(:instance).returns(@ca) end it "should send the :destroy order to the ca if we are a CA" do Puppet::SSL::CertificateAuthority.stubs(:ca?).returns(true) @ca.expects(:revoke).with(@host) @ca.expects(:destroy).with(@host) subject.clean_cert(@host) end it "should not destroy the certs if we are not a CA" do Puppet::SSL::CertificateAuthority.stubs(:ca?).returns(false) @ca.expects(:revoke).never @ca.expects(:destroy).never subject.clean_cert(@host) end end describe "when cleaning cached facts" do it "should destroy facts" do @host = 'node' Puppet::Node::Facts.indirection.expects(:destroy).with(@host) subject.clean_cached_facts(@host) end end describe "when cleaning cached node" do it "should destroy the cached node" do Puppet::Node.indirection.expects(:destroy).with(@host) subject.clean_cached_node(@host) end end describe "when cleaning archived reports" do it "should tell the reports to remove themselves" do Puppet::Transaction::Report.indirection.stubs(:destroy).with(@host) subject.clean_reports(@host) end end describe "when cleaning storeconfigs entries for host", :if => Puppet.features.rails? do before :each do # Stub this so we don't need access to the DB require 'puppet/rails/host' Puppet.stubs(:[]).with(:storeconfigs).returns(true) Puppet::Rails.stubs(:connect) @rails_node = stub_everything 'rails_node' Puppet::Rails::Host.stubs(:find_by_name).returns(@rails_node) end it "should connect to the database" do Puppet::Rails.expects(:connect) subject.clean_storeconfigs(@host, false) end it "should find the right host entry" do Puppet::Rails::Host.expects(:find_by_name).with(@host).returns(@rails_node) subject.clean_storeconfigs(@host, false) end describe "without unexport" do it "should remove the host and it's content" do @rails_node.expects(:destroy) subject.clean_storeconfigs(@host, false) end end describe "with unexport" do before :each do @rails_node.stubs(:id).returns(1234) @type = stub_everything 'type' @type.stubs(:validattr?).with(:ensure).returns(true) @ensure_name = stub_everything 'ensure_name', :id => 23453 Puppet::Rails::ParamName.stubs(:find_or_create_by_name).returns(@ensure_name) @param_values = stub_everything 'param_values' @resource = stub_everything 'resource', :param_values => @param_values, :restype => "File" Puppet::Rails::Resource.stubs(:find).returns([@resource]) end it "should find all resources" do Puppet::Rails::Resource.expects(:find).with(:all, {:include => {:param_values => :param_name}, :conditions => ["exported=? AND host_id=?", true, 1234]}).returns([]) subject.clean_storeconfigs(@host, true) end describe "with an exported native type" do before :each do Puppet::Type.stubs(:type).returns(@type) @type.expects(:validattr?).with(:ensure).returns(true) end it "should test a native type for ensure as an attribute" do subject.clean_storeconfigs(@host, true) end it "should delete the old ensure parameter" do ensure_param = stub 'ensure_param', :id => 12345, :line => 12 @param_values.stubs(:find).returns(ensure_param) Puppet::Rails::ParamValue.expects(:delete).with(12345); subject.clean_storeconfigs(@host, true) end it "should add an ensure => absent parameter" do @param_values.expects(:create).with(:value => "absent", :line => 0, :param_name => @ensure_name) subject.clean_storeconfigs(@host, true) end end describe "with an exported definition" do it "should try to lookup a definition and test it for the ensure argument" do Puppet::Type.stubs(:type).returns(nil) definition = stub_everything 'definition', :arguments => { 'ensure' => 'present' } Puppet::Resource::TypeCollection.any_instance.expects(:find_definition).with('', "File").returns(definition) subject.clean_storeconfigs(@host, true) end end it "should not unexport the resource of an unkown type" do Puppet::Type.stubs(:type).returns(nil) Puppet::Resource::TypeCollection.any_instance.expects(:find_definition).with('', "File").returns(nil) Puppet::Rails::ParamName.expects(:find_or_create_by_name).never subject.clean_storeconfigs(@host, true) end it "should not unexport the resource of a not ensurable native type" do Puppet::Type.stubs(:type).returns(@type) @type.expects(:validattr?).with(:ensure).returns(false) Puppet::Resource::TypeCollection.any_instance.expects(:find_definition).with('', "File").returns(nil) Puppet::Rails::ParamName.expects(:find_or_create_by_name).never subject.clean_storeconfigs(@host, true) end it "should not unexport the resource of a not ensurable definition" do Puppet::Type.stubs(:type).returns(nil) definition = stub_everything 'definition', :arguments => { 'foobar' => 'someValue' } Puppet::Resource::TypeCollection.any_instance.expects(:find_definition).with('', "File").returns(definition) Puppet::Rails::ParamName.expects(:find_or_create_by_name).never subject.clean_storeconfigs(@host, true) end end end end end end diff --git a/spec/unit/indirector/certificate_request/ca_spec.rb b/spec/unit/indirector/certificate_request/ca_spec.rb index e5443a26d..3fe8fb3b7 100755 --- a/spec/unit/indirector/certificate_request/ca_spec.rb +++ b/spec/unit/indirector/certificate_request/ca_spec.rb @@ -1,57 +1,57 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/ssl/host' require 'puppet/indirector/certificate_request/ca' describe Puppet::SSL::CertificateRequest::Ca, :unless => Puppet.features.microsoft_windows? do include PuppetSpec::Files before :each do Puppet[:ssldir] = tmpdir('ssl') Puppet::SSL::Host.ca_location = :local Puppet[:localcacert] = Puppet[:cacert] @ca = Puppet::SSL::CertificateAuthority.new end after :all do Puppet::SSL::Host.ca_location = :none end it "should have documentation" do Puppet::SSL::CertificateRequest::Ca.doc.should be_instance_of(String) end it "should use the :csrdir as the collection directory" do Puppet.settings.expects(:value).with(:csrdir).returns "/request/dir" Puppet::SSL::CertificateRequest::Ca.collection_directory.should == "/request/dir" end it "should overwrite the previous certificate request if allow_duplicate_certs is true" do Puppet[:allow_duplicate_certs] = true host = Puppet::SSL::Host.new("foo") host.generate_certificate_request @ca.sign(host.name) Puppet::SSL::Host.indirection.find("foo").generate_certificate_request Puppet::SSL::Certificate.indirection.find("foo").name.should == "foo" Puppet::SSL::CertificateRequest.indirection.find("foo").name.should == "foo" Puppet::SSL::Host.indirection.find("foo").state.should == "requested" end it "should reject a new certificate request if allow_duplicate_certs is false" do Puppet[:allow_duplicate_certs] = false host = Puppet::SSL::Host.new("bar") host.generate_certificate_request @ca.sign(host.name) - expect { Puppet::SSL::Host.indirection.find("bar").generate_certificate_request }.should raise_error(/ignoring certificate request/) + expect { Puppet::SSL::Host.indirection.find("bar").generate_certificate_request }.to raise_error(/ignoring certificate request/) Puppet::SSL::Certificate.indirection.find("bar").name.should == "bar" Puppet::SSL::CertificateRequest.indirection.find("bar").should be_nil Puppet::SSL::Host.indirection.find("bar").state.should == "signed" end end diff --git a/spec/unit/indirector/facts/inventory_service_spec.rb b/spec/unit/indirector/facts/inventory_service_spec.rb index f44934abc..df6b7ddf5 100644 --- a/spec/unit/indirector/facts/inventory_service_spec.rb +++ b/spec/unit/indirector/facts/inventory_service_spec.rb @@ -1,22 +1,22 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/indirector/facts/inventory_service' describe Puppet::Node::Facts::InventoryService do it "should suppress failures and warn when saving facts" do facts = Puppet::Node::Facts.new('foo') request = Puppet::Indirector::Request.new(:facts, :save, facts) Net::HTTP.any_instance.stubs(:put).raises(Errno::ECONNREFUSED) Puppet.expects(:warning).with do |msg| msg =~ /Could not upload facts for foo to inventory service/ end expect { subject.save(request) - }.should_not raise_error + }.to_not raise_error end end diff --git a/spec/unit/indirector/queue_spec.rb b/spec/unit/indirector/queue_spec.rb index eba136bbc..a27f4b083 100755 --- a/spec/unit/indirector/queue_spec.rb +++ b/spec/unit/indirector/queue_spec.rb @@ -1,121 +1,121 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/indirector/queue' class Puppet::Indirector::Queue::TestClient end class FooExampleData attr_accessor :name def self.pson_create(pson) new(pson['data'].to_sym) end def initialize(name = nil) @name = name if name end def render(format = :pson) to_pson end def to_pson(*args) {:type => self.class.to_s, :data => name}.to_pson(*args) end end describe Puppet::Indirector::Queue, :if => Puppet.features.pson? do before :each do @model = mock 'model' @indirection = stub 'indirection', :name => :my_queue, :register_terminus_type => nil, :model => @model Puppet::Indirector::Indirection.stubs(:instance).with(:my_queue).returns(@indirection) module MyQueue; end @store_class = class MyQueue::MyType < Puppet::Indirector::Queue self end @store = @store_class.new @subject_class = FooExampleData @subject = @subject_class.new @subject.name = :me Puppet[:queue_type] = :test_client Puppet::Util::Queue.stubs(:queue_type_to_class).with(:test_client).returns(Puppet::Indirector::Queue::TestClient) @request = stub 'request', :key => :me, :instance => @subject end it "should require PSON" do Puppet.features.expects(:pson?).returns false - lambda { @store_class.new }.should raise_error(ArgumentError) + expect { @store_class.new }.to raise_error(ArgumentError) end it 'should use the correct client type and queue' do @store.queue.should == :my_queue @store.client.should be_an_instance_of(Puppet::Indirector::Queue::TestClient) end describe "when saving" do it 'should render the instance using pson' do @subject.expects(:render).with(:pson) @store.client.stubs(:publish_message) @store.save(@request) end it "should publish the rendered message to the appropriate queue on the client" do @subject.expects(:render).returns "mypson" @store.client.expects(:publish_message).with(:my_queue, "mypson") @store.save(@request) end it "should catch any exceptions raised" do @store.client.expects(:publish_message).raises ArgumentError - lambda { @store.save(@request) }.should raise_error(Puppet::Error) + expect { @store.save(@request) }.to raise_error(Puppet::Error) end end describe "when subscribing to the queue" do before do @store_class.stubs(:model).returns @model end it "should use the model's Format support to intern the message from pson" do @model.expects(:convert_from).with(:pson, "mymessage") @store_class.client.expects(:subscribe).yields("mymessage") @store_class.subscribe {|o| o } end it "should yield each interned received message" do @model.stubs(:convert_from).returns "something" @subject_two = @subject_class.new @subject_two.name = :too @store_class.client.expects(:subscribe).with(:my_queue).multiple_yields(@subject, @subject_two) received = [] @store_class.subscribe do |obj| received.push(obj) end received.should == %w{something something} end it "should log but not propagate errors" do @store_class.client.expects(:subscribe).yields("foo") @store_class.expects(:intern).raises(ArgumentError) - expect { @store_class.subscribe {|o| o } }.should_not raise_error + expect { @store_class.subscribe {|o| o } }.to_not raise_error @logs.length.should == 1 @logs.first.message.should =~ /Error occured with subscription to queue my_queue for indirection my_queue: ArgumentError/ @logs.first.level.should == :err end end end diff --git a/spec/unit/indirector/rest_spec.rb b/spec/unit/indirector/rest_spec.rb index f18e99a3c..7954baaed 100755 --- a/spec/unit/indirector/rest_spec.rb +++ b/spec/unit/indirector/rest_spec.rb @@ -1,567 +1,578 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/indirector/rest' shared_examples_for "a REST http call" do it "should accept a path" do lambda { @search.send(@method, *@arguments) }.should_not raise_error(ArgumentError) end it "should require a path" do lambda { @searcher.send(@method) }.should raise_error(ArgumentError) end it "should return the results of deserializing the response to the request" do conn = mock 'connection' conn.stubs(:put).returns @response conn.stubs(:delete).returns @response conn.stubs(:get).returns @response Puppet::Network::HttpPool.stubs(:http_instance).returns conn @searcher.expects(:deserialize).with(@response).returns "myobject" @searcher.send(@method, *@arguments).should == 'myobject' end end describe Puppet::Indirector::REST do before :all do Puppet::Indirector::Terminus.stubs(:register_terminus_class) @model = stub('model', :supported_formats => %w{}, :convert_from => nil) @instance = stub('model instance', :name= => nil) @indirection = stub('indirection', :name => :mystuff, :register_terminus_type => nil, :model => @model) Puppet::Indirector::Indirection.expects(:instance).returns(@indirection) module This module Is module A module Test end end end end @rest_class = class This::Is::A::Test::Class < Puppet::Indirector::REST self end end before :each do @response = stub('mock response', :body => 'result', :code => "200") @response.stubs(:[]).with('content-type').returns "text/plain" @response.stubs(:[]).with('content-encoding').returns nil @searcher = @rest_class.new @searcher.stubs(:model).returns @model end it "should include the v1 REST API module" do Puppet::Indirector::REST.ancestors.should be_include(Puppet::Network::HTTP::API::V1) end it "should have a method for specifying what setting a subclass should use to retrieve its server" do @rest_class.should respond_to(:use_server_setting) end it "should use any specified setting to pick the server" do @rest_class.expects(:server_setting).returns :servset Puppet.settings.expects(:value).with(:servset).returns "myserver" @rest_class.server.should == "myserver" end it "should default to :server for the server setting" do @rest_class.expects(:server_setting).returns nil Puppet.settings.expects(:value).with(:server).returns "myserver" @rest_class.server.should == "myserver" end it "should have a method for specifying what setting a subclass should use to retrieve its port" do @rest_class.should respond_to(:use_port_setting) end it "should use any specified setting to pick the port" do @rest_class.expects(:port_setting).returns :servset Puppet.settings.expects(:value).with(:servset).returns "321" @rest_class.port.should == 321 end it "should default to :port for the port setting" do @rest_class.expects(:port_setting).returns nil Puppet.settings.expects(:value).with(:masterport).returns "543" @rest_class.port.should == 543 end describe "when making http requests" do include PuppetSpec::Files - it "should provide a suggestive error message when certificate verify failed" do + def a_connection_that_verifies(args) connection = Net::HTTP.new('my_server', 8140) - @searcher.stubs(:network).returns(connection) + connection.stubs(:get).with do + connection.verify_callback.call(args[:has_passed_pre_checks], args[:in_context]) + true + end.raises(OpenSSL::SSL::SSLError.new(args[:fails_with])) + connection + end - connection.stubs(:get).raises(OpenSSL::SSL::SSLError.new('certificate verify failed')) + def a_store_context(args) + Puppet[:confdir] = tmpdir('conf') + cert = Puppet::SSL::CertificateAuthority.new.generate(args[:for_server], :dns_alt_names => args[:for_aliases]).content + ssl_context = mock('OpenSSL::X509::StoreContext') + ssl_context.stubs(:current_cert).returns(cert) + ssl_context.stubs(:error_string).returns(args[:with_error_string]) + ssl_context + end + it "should provide a useful error message when one is available and certificate validation fails", :unless => Puppet.features.microsoft_windows? do + @searcher.stubs(:network). + returns(a_connection_that_verifies(:has_passed_pre_checks => false, + :in_context => a_store_context(:for_server => 'not_my_server', + :with_error_string => 'shady looking signature'), + :fails_with => 'certificate verify failed')) expect do @searcher.http_request(:get, stub('request')) - end.to raise_error(/This is often because the time is out of sync on the server or client/) + end.to raise_error(Puppet::Error, "certificate verify failed: [shady looking signature for /CN=not_my_server]") end it "should provide a helpful error message when hostname was not match with server certificate", :unless => Puppet.features.microsoft_windows? do - Puppet[:confdir] = tmpdir('conf') - cert = Puppet::SSL::CertificateAuthority.new.generate('not_my_server', :dns_alt_names => 'foo,bar,baz').content - - connection = Net::HTTP.new('my_server', 8140) - @searcher.stubs(:network).returns(connection) - ssl_context = OpenSSL::SSL::SSLContext.new - ssl_context.stubs(:current_cert).returns(cert) - connection.stubs(:get).with do - connection.verify_callback.call(true, ssl_context) - end.raises(OpenSSL::SSL::SSLError.new('hostname was not match with server certificate')) - - msg = /Server hostname 'my_server' did not match server certificate; expected one of (.+)/ - expect { @searcher.http_request(:get, stub('request')) }.to( - raise_error(Puppet::Error, msg) do |error| - error.message =~ msg + @searcher.stubs(:network). + returns(a_connection_that_verifies(:has_passed_pre_checks => true, + :in_context => a_store_context(:for_server => 'not_my_server', + :for_aliases => 'foo,bar,baz'), + :fails_with => 'hostname was not match with server certificate')) + + expect { @searcher.http_request(:get, stub('request')) }. + to raise_error(Puppet::Error) do |error| + error.message =~ /Server hostname 'my_server' did not match server certificate; expected one of (.+)/ $1.split(', ').should =~ %w[DNS:foo DNS:bar DNS:baz DNS:not_my_server not_my_server] end - ) end it "should pass along the error message otherwise" do connection = Net::HTTP.new('my_server', 8140) @searcher.stubs(:network).returns(connection) connection.stubs(:get).raises(OpenSSL::SSL::SSLError.new('some other message')) expect do @searcher.http_request(:get, stub('request')) end.to raise_error(/some other message/) end end describe "when deserializing responses" do it "should return nil if the response code is 404" do response = mock 'response' response.expects(:code).returns "404" @searcher.deserialize(response).should be_nil end [300,400,403,405,500,501,502,503,504].each { |rc| describe "when the response code is #{rc}" do before :each do @model.expects(:convert_from).never @response = mock 'response' @response.stubs(:code).returns rc.to_s @response.stubs(:[]).with('content-encoding').returns nil @response.stubs(:message).returns "There was a problem (header)" end it "should fail" do @response.stubs(:body).returns nil lambda { @searcher.deserialize(@response) }.should raise_error(Net::HTTPError) end it "should take the error message from the body, if present" do @response.stubs(:body).returns "There was a problem (body)" lambda { @searcher.deserialize(@response) }.should raise_error(Net::HTTPError,"Error #{rc} on SERVER: There was a problem (body)") end it "should take the error message from the response header if the body is empty" do @response.stubs(:body).returns "" lambda { @searcher.deserialize(@response) }.should raise_error(Net::HTTPError,"Error #{rc} on SERVER: There was a problem (header)") end it "should take the error message from the response header if the body is absent" do @response.stubs(:body).returns nil lambda { @searcher.deserialize(@response) }.should raise_error(Net::HTTPError,"Error #{rc} on SERVER: There was a problem (header)") end describe "and with http compression" do it "should uncompress the body" do @response.stubs(:body).returns("compressed body") @searcher.expects(:uncompress_body).with(@response).returns("uncompressed") lambda { @searcher.deserialize(@response) }.should raise_error { |e| e.message =~ /uncompressed/ } end end end } it "should return the results of converting from the format specified by the content-type header if the response code is in the 200s" do @model.expects(:convert_from).with("myformat", "mydata").returns "myobject" response = mock 'response' response.stubs(:[]).with("content-type").returns "myformat" response.stubs(:[]).with("content-encoding").returns nil response.stubs(:body).returns "mydata" response.stubs(:code).returns "200" @searcher.deserialize(response).should == "myobject" end it "should convert and return multiple instances if the return code is in the 200s and 'multiple' is specified" do @model.expects(:convert_from_multiple).with("myformat", "mydata").returns "myobjects" response = mock 'response' response.stubs(:[]).with("content-type").returns "myformat" response.stubs(:[]).with("content-encoding").returns nil response.stubs(:body).returns "mydata" response.stubs(:code).returns "200" @searcher.deserialize(response, true).should == "myobjects" end it "should strip the content-type header to keep only the mime-type" do @model.expects(:convert_from).with("text/plain", "mydata").returns "myobject" response = mock 'response' response.stubs(:[]).with("content-type").returns "text/plain; charset=utf-8" response.stubs(:[]).with("content-encoding").returns nil response.stubs(:body).returns "mydata" response.stubs(:code).returns "200" @searcher.deserialize(response) end it "should uncompress the body" do @model.expects(:convert_from).with("myformat", "uncompressed mydata").returns "myobject" response = mock 'response' response.stubs(:[]).with("content-type").returns "myformat" response.stubs(:body).returns "compressed mydata" response.stubs(:code).returns "200" @searcher.expects(:uncompress_body).with(response).returns("uncompressed mydata") @searcher.deserialize(response).should == "myobject" end end describe "when creating an HTTP client" do before do Puppet.settings.stubs(:value).returns("rest_testing") end it "should use the class's server and port if the indirection request provides neither" do @request = stub 'request', :key => "foo", :server => nil, :port => nil @searcher.class.expects(:port).returns 321 @searcher.class.expects(:server).returns "myserver" Puppet::Network::HttpPool.expects(:http_instance).with("myserver", 321).returns "myconn" @searcher.network(@request).should == "myconn" end it "should use the server from the indirection request if one is present" do @request = stub 'request', :key => "foo", :server => "myserver", :port => nil @searcher.class.stubs(:port).returns 321 Puppet::Network::HttpPool.expects(:http_instance).with("myserver", 321).returns "myconn" @searcher.network(@request).should == "myconn" end it "should use the port from the indirection request if one is present" do @request = stub 'request', :key => "foo", :server => nil, :port => 321 @searcher.class.stubs(:server).returns "myserver" Puppet::Network::HttpPool.expects(:http_instance).with("myserver", 321).returns "myconn" @searcher.network(@request).should == "myconn" end end describe "when doing a find" do before :each do @connection = stub('mock http connection', :get => @response, :verify_callback= => nil) @searcher.stubs(:network).returns(@connection) # neuter the network connection # Use a key with spaces, so we can test escaping @request = Puppet::Indirector::Request.new(:foo, :find, "foo bar", :environment => "myenv") end describe "with a large body" do it "should use the POST http method" do params = {} 'aa'.upto('zz') do |s| params[s] = 'foo' end # The request special-cases this parameter, and it # won't be passed on to the server, so we remove it here # to avoid a failure. params.delete('ip') @request = Puppet::Indirector::Request.new(:foo, :find, "foo bar", params.merge(:environment => "myenv")) @connection.expects(:post).with do |uri, body| uri == "/myenv/foo/foo%20bar" and body.split("&").sort == params.map {|key,value| "#{key}=#{value}"}.sort end.returns(@response) @searcher.find(@request) end end describe "with a small body" do it "should use the GET http method" do @searcher.expects(:network).returns @connection @connection.expects(:get).returns @response @searcher.find(@request) end end it "should deserialize and return the http response, setting name" do @connection.expects(:get).returns @response instance = stub 'object' instance.expects(:name=) @searcher.expects(:deserialize).with(@response).returns instance @searcher.find(@request).should == instance end it "should deserialize and return the http response, and not require name=" do @connection.expects(:get).returns @response instance = stub 'object' @searcher.expects(:deserialize).with(@response).returns instance @searcher.find(@request).should == instance end it "should use the URI generated by the Handler module" do @connection.expects(:get).with { |path, args| path == "/myenv/foo/foo%20bar?" }.returns(@response) @searcher.find(@request) end it "should provide an Accept header containing the list of supported formats joined with commas" do @connection.expects(:get).with { |path, args| args["Accept"] == "supported, formats" }.returns(@response) @searcher.model.expects(:supported_formats).returns %w{supported formats} @searcher.find(@request) end it "should add Accept-Encoding header" do @searcher.expects(:add_accept_encoding).returns({"accept-encoding" => "gzip"}) @connection.expects(:get).with { |path, args| args["accept-encoding"] == "gzip" }.returns(@response) @searcher.find(@request) end it "should deserialize and return the network response" do @searcher.expects(:deserialize).with(@response).returns @instance @searcher.find(@request).should equal(@instance) end it "should set the name of the resulting instance to the asked-for name" do @searcher.expects(:deserialize).with(@response).returns @instance @instance.expects(:name=).with "foo bar" @searcher.find(@request) end it "should generate an error when result data deserializes fails" do @searcher.expects(:deserialize).raises(ArgumentError) lambda { @searcher.find(@request) }.should raise_error(ArgumentError) end end describe "when doing a head" do before :each do @connection = stub('mock http connection', :head => @response, :verify_callback= => nil) @searcher.stubs(:network).returns(@connection) # Use a key with spaces, so we can test escaping @request = Puppet::Indirector::Request.new(:foo, :head, "foo bar") end it "should call the HEAD http method on a network connection" do @searcher.expects(:network).returns @connection @connection.expects(:head).returns @response @searcher.head(@request) end it "should return true if there was a successful http response" do @connection.expects(:head).returns @response @response.stubs(:code).returns "200" @searcher.head(@request).should == true end it "should return false if there was a successful http response" do @connection.expects(:head).returns @response @response.stubs(:code).returns "404" @searcher.head(@request).should == false end it "should use the URI generated by the Handler module" do @searcher.expects(:indirection2uri).with(@request).returns "/my/uri" @connection.expects(:head).with { |path, args| path == "/my/uri" }.returns(@response) @searcher.head(@request) end end describe "when doing a search" do before :each do @connection = stub('mock http connection', :get => @response, :verify_callback= => nil) @searcher.stubs(:network).returns(@connection) # neuter the network connection @model.stubs(:convert_from_multiple) @request = Puppet::Indirector::Request.new(:foo, :search, "foo bar") end it "should call the GET http method on a network connection" do @searcher.expects(:network).returns @connection @connection.expects(:get).returns @response @searcher.search(@request) end it "should deserialize as multiple instances and return the http response" do @connection.expects(:get).returns @response @searcher.expects(:deserialize).with(@response, true).returns "myobject" @searcher.search(@request).should == 'myobject' end it "should use the URI generated by the Handler module" do @searcher.expects(:indirection2uri).with(@request).returns "/mys/uri" @connection.expects(:get).with { |path, args| path == "/mys/uri" }.returns(@response) @searcher.search(@request) end it "should provide an Accept header containing the list of supported formats joined with commas" do @connection.expects(:get).with { |path, args| args["Accept"] == "supported, formats" }.returns(@response) @searcher.model.expects(:supported_formats).returns %w{supported formats} @searcher.search(@request) end it "should return an empty array if serialization returns nil" do @model.stubs(:convert_from_multiple).returns nil @searcher.search(@request).should == [] end it "should generate an error when result data deserializes fails" do @searcher.expects(:deserialize).raises(ArgumentError) lambda { @searcher.search(@request) }.should raise_error(ArgumentError) end end describe "when doing a destroy" do before :each do @connection = stub('mock http connection', :delete => @response, :verify_callback= => nil) @searcher.stubs(:network).returns(@connection) # neuter the network connection @request = Puppet::Indirector::Request.new(:foo, :destroy, "foo bar") end it "should call the DELETE http method on a network connection" do @searcher.expects(:network).returns @connection @connection.expects(:delete).returns @response @searcher.destroy(@request) end it "should fail if any options are provided, since DELETE apparently does not support query options" do @request.stubs(:options).returns(:one => "two", :three => "four") lambda { @searcher.destroy(@request) }.should raise_error(ArgumentError) end it "should deserialize and return the http response" do @connection.expects(:delete).returns @response @searcher.expects(:deserialize).with(@response).returns "myobject" @searcher.destroy(@request).should == 'myobject' end it "should use the URI generated by the Handler module" do @searcher.expects(:indirection2uri).with(@request).returns "/my/uri" @connection.expects(:delete).with { |path, args| path == "/my/uri" }.returns(@response) @searcher.destroy(@request) end it "should not include the query string" do @connection.stubs(:delete).returns @response @searcher.destroy(@request) end it "should provide an Accept header containing the list of supported formats joined with commas" do @connection.expects(:delete).with { |path, args| args["Accept"] == "supported, formats" }.returns(@response) @searcher.model.expects(:supported_formats).returns %w{supported formats} @searcher.destroy(@request) end it "should deserialize and return the network response" do @searcher.expects(:deserialize).with(@response).returns @instance @searcher.destroy(@request).should equal(@instance) end it "should generate an error when result data deserializes fails" do @searcher.expects(:deserialize).raises(ArgumentError) lambda { @searcher.destroy(@request) }.should raise_error(ArgumentError) end end describe "when doing a save" do before :each do @connection = stub('mock http connection', :put => @response, :verify_callback= => nil) @searcher.stubs(:network).returns(@connection) # neuter the network connection @instance = stub 'instance', :render => "mydata", :mime => "mime" @request = Puppet::Indirector::Request.new(:foo, :save, "foo bar") @request.instance = @instance end it "should call the PUT http method on a network connection" do @searcher.expects(:network).returns @connection @connection.expects(:put).returns @response @searcher.save(@request) end it "should fail if any options are provided, since DELETE apparently does not support query options" do @request.stubs(:options).returns(:one => "two", :three => "four") lambda { @searcher.save(@request) }.should raise_error(ArgumentError) end it "should use the URI generated by the Handler module" do @searcher.expects(:indirection2uri).with(@request).returns "/my/uri" @connection.expects(:put).with { |path, args| path == "/my/uri" }.returns(@response) @searcher.save(@request) end it "should serialize the instance using the default format and pass the result as the body of the request" do @instance.expects(:render).returns "serial_instance" @connection.expects(:put).with { |path, data, args| data == "serial_instance" }.returns @response @searcher.save(@request) end it "should deserialize and return the http response" do @connection.expects(:put).returns @response @searcher.expects(:deserialize).with(@response).returns "myobject" @searcher.save(@request).should == 'myobject' end it "should provide an Accept header containing the list of supported formats joined with commas" do @connection.expects(:put).with { |path, data, args| args["Accept"] == "supported, formats" }.returns(@response) @searcher.model.expects(:supported_formats).returns %w{supported formats} @searcher.save(@request) end it "should provide a Content-Type header containing the mime-type of the sent object" do @connection.expects(:put).with { |path, data, args| args['Content-Type'] == "mime" }.returns(@response) @instance.expects(:mime).returns "mime" @searcher.save(@request) end it "should deserialize and return the network response" do @searcher.expects(:deserialize).with(@response).returns @instance @searcher.save(@request).should equal(@instance) end it "should generate an error when result data deserializes fails" do @searcher.expects(:deserialize).raises(ArgumentError) lambda { @searcher.save(@request) }.should raise_error(ArgumentError) end end end diff --git a/spec/unit/indirector_spec.rb b/spec/unit/indirector_spec.rb index 0c09831db..6349cb777 100755 --- a/spec/unit/indirector_spec.rb +++ b/spec/unit/indirector_spec.rb @@ -1,144 +1,144 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/defaults' require 'puppet/indirector' describe Puppet::Indirector, "when configuring routes" do before :each do Puppet::Node.indirection.reset_terminus_class Puppet::Node.indirection.cache_class = nil end after :each do Puppet::Node.indirection.reset_terminus_class Puppet::Node.indirection.cache_class = nil end it "should configure routes as requested" do routes = { "node" => { "terminus" => "exec", "cache" => "plain" } } Puppet::Indirector.configure_routes(routes) Puppet::Node.indirection.terminus_class.should == "exec" Puppet::Node.indirection.cache_class.should == "plain" end it "should fail when given an invalid indirection" do routes = { "fake_indirection" => { "terminus" => "exec", "cache" => "plain" } } - expect { Puppet::Indirector.configure_routes(routes) }.should raise_error(/fake_indirection does not exist/) + expect { Puppet::Indirector.configure_routes(routes) }.to raise_error(/fake_indirection does not exist/) end it "should fail when given an invalid terminus" do routes = { "node" => { "terminus" => "fake_terminus", "cache" => "plain" } } - expect { Puppet::Indirector.configure_routes(routes) }.should raise_error(/Could not find terminus fake_terminus/) + expect { Puppet::Indirector.configure_routes(routes) }.to raise_error(/Could not find terminus fake_terminus/) end it "should fail when given an invalid cache" do routes = { "node" => { "terminus" => "exec", "cache" => "fake_cache" } } - expect { Puppet::Indirector.configure_routes(routes) }.should raise_error(/Could not find terminus fake_cache/) + expect { Puppet::Indirector.configure_routes(routes) }.to raise_error(/Could not find terminus fake_cache/) end end describe Puppet::Indirector, " when available to a model" do before do @thingie = Class.new do extend Puppet::Indirector end end it "should provide a way for the model to register an indirection under a name" do @thingie.should respond_to(:indirects) end end describe Puppet::Indirector, "when registering an indirection" do before do @thingie = Class.new do extend Puppet::Indirector attr_reader :name def initialize(name) @name = name end end end it "should require a name when registering a model" do - Proc.new {@thingie.send(:indirects) }.should raise_error(ArgumentError) + expect {@thingie.send(:indirects) }.to raise_error(ArgumentError) end it "should create an indirection instance to manage each indirecting model" do @indirection = @thingie.indirects(:test) @indirection.should be_instance_of(Puppet::Indirector::Indirection) end it "should not allow a model to register under multiple names" do # Keep track of the indirection instance so we can delete it on cleanup @indirection = @thingie.indirects :first - Proc.new { @thingie.indirects :second }.should raise_error(ArgumentError) + expect { @thingie.indirects :second }.to raise_error(ArgumentError) end it "should make the indirection available via an accessor" do @indirection = @thingie.indirects :first @thingie.indirection.should equal(@indirection) end it "should pass any provided options to the indirection during initialization" do klass = mock 'terminus class' Puppet::Indirector::Indirection.expects(:new).with(@thingie, :first, {:some => :options}) @indirection = @thingie.indirects :first, :some => :options end it "should extend the class with the Format Handler" do @indirection = @thingie.indirects :first @thingie.singleton_class.ancestors.should be_include(Puppet::Network::FormatHandler) end after do @indirection.delete if @indirection end end describe Puppet::Indirector, "when redirecting a model" do before do @thingie = Class.new do extend Puppet::Indirector attr_reader :name def initialize(name) @name = name end end @indirection = @thingie.send(:indirects, :test) end it "should include the Envelope module in the model" do @thingie.ancestors.should be_include(Puppet::Indirector::Envelope) end after do @indirection.delete end end diff --git a/spec/unit/interface/action_builder_spec.rb b/spec/unit/interface/action_builder_spec.rb index f779a5461..437a03e88 100755 --- a/spec/unit/interface/action_builder_spec.rb +++ b/spec/unit/interface/action_builder_spec.rb @@ -1,216 +1,217 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/interface/action_builder' require 'puppet/network/format_handler' describe Puppet::Interface::ActionBuilder do let :face do Puppet::Interface.new(:puppet_interface_actionbuilder, '0.0.1') end it "should build an action" do action = Puppet::Interface::ActionBuilder.build(face, :foo) do when_invoked do |options| true end end action.should be_a(Puppet::Interface::Action) action.name.should == :foo end it "should define a method on the face which invokes the action" do face = Puppet::Interface.new(:action_builder_test_interface, '0.0.1') do action(:foo) { when_invoked { |options| "invoked the method" } } end face.foo.should == "invoked the method" end it "should require a block" do - expect { Puppet::Interface::ActionBuilder.build(nil, :foo) }. - should raise_error("Action :foo must specify a block") + expect { + Puppet::Interface::ActionBuilder.build(nil, :foo) + }.to raise_error("Action :foo must specify a block") end it "should require an invocation block" do expect { Puppet::Interface::ActionBuilder.build(face, :foo) {} }.to raise_error(/actions need to know what to do when_invoked; please add the block/) end describe "when handling options" do it "should have a #option DSL function" do method = nil Puppet::Interface::ActionBuilder.build(face, :foo) do when_invoked do |options| true end method = self.method(:option) end method.should be_an_instance_of Method end it "should define an option without a block" do action = Puppet::Interface::ActionBuilder.build(face, :foo) do when_invoked do |options| true end option "--bar" end action.should be_option :bar end it "should accept an empty block" do action = Puppet::Interface::ActionBuilder.build(face, :foo) do when_invoked do |options| true end option "--bar" do # This space left deliberately blank. end end action.should be_option :bar end end context "inline documentation" do it "should set the summary" do action = Puppet::Interface::ActionBuilder.build(face, :foo) do when_invoked do |options| true end summary "this is some text" end action.summary.should == "this is some text" end end context "action defaulting" do it "should set the default to true" do action = Puppet::Interface::ActionBuilder.build(face, :foo) do when_invoked do |options| true end default end action.default.should be_true end it "should not be default by, er, default. *cough*" do action = Puppet::Interface::ActionBuilder.build(face, :foo) do when_invoked do |options| true end end action.default.should be_false end end context "#when_rendering" do it "should fail if no rendering format is given" do expect { Puppet::Interface::ActionBuilder.build(face, :foo) do when_invoked do |options| true end when_rendering do true end end }.to raise_error ArgumentError, /must give a rendering format to when_rendering/ end it "should fail if no block is given" do expect { Puppet::Interface::ActionBuilder.build(face, :foo) do when_invoked do |options| true end when_rendering :json end }.to raise_error ArgumentError, /must give a block to when_rendering/ end it "should fail if the block takes no arguments" do expect { Puppet::Interface::ActionBuilder.build(face, :foo) do when_invoked do |options| true end when_rendering :json do true end end }.to raise_error ArgumentError, /the puppet_interface_actionbuilder face foo action takes .* not/ end it "should fail if the when_rendering block takes a different number of arguments than when_invoked" do expect { Puppet::Interface::ActionBuilder.build(face, :foo) do when_invoked do |options| true end when_rendering :json do |a, b, c| true end end }.to raise_error ArgumentError, /the puppet_interface_actionbuilder face foo action takes .* not 3/ end it "should fail if the block takes a variable number of arguments" do expect { Puppet::Interface::ActionBuilder.build(face, :foo) do when_invoked do |options| true end when_rendering :json do |*args| true end end }.to raise_error ArgumentError, /the puppet_interface_actionbuilder face foo action takes .* not/ end it "should stash a rendering block" do action = Puppet::Interface::ActionBuilder.build(face, :foo) do when_invoked do |options| true end when_rendering :json do |a| true end end action.when_rendering(:json).should be_an_instance_of Method end it "should fail if you try to set the same rendering twice" do expect { Puppet::Interface::ActionBuilder.build(face, :foo) do when_invoked do |options| true end when_rendering :json do |a| true end when_rendering :json do |a| true end end }.to raise_error ArgumentError, /You can't define a rendering method for json twice/ end it "should work if you set two different renderings" do action = Puppet::Interface::ActionBuilder.build(face, :foo) do when_invoked do |options| true end when_rendering :json do |a| true end when_rendering :yaml do |a| true end end action.when_rendering(:json).should be_an_instance_of Method action.when_rendering(:yaml).should be_an_instance_of Method end it "should be bound to the face when called" do action = Puppet::Interface::ActionBuilder.build(face, :foo) do when_invoked do |options| true end when_rendering :json do |a| self end end action.when_rendering(:json).call(true).should == face end end context "#render_as" do it "should default to nil (eg: based on context)" do action = Puppet::Interface::ActionBuilder.build(face, :foo) do when_invoked do |options| true end end action.render_as.should be_nil end it "should fail if not rendering format is given" do expect { Puppet::Interface::ActionBuilder.build(face, :foo) do when_invoked do |options| true end render_as end }.to raise_error ArgumentError, /must give a rendering format to render_as/ end Puppet::Network::FormatHandler.formats.each do |name| it "should accept #{name.inspect} format" do action = Puppet::Interface::ActionBuilder.build(face, :foo) do when_invoked do |options| true end render_as name end action.render_as.should == name end end [:if_you_define_this_format_you_frighten_me, "json", 12].each do |input| it "should fail if given #{input.inspect}" do expect { Puppet::Interface::ActionBuilder.build(face, :foo) do when_invoked do |options| true end render_as input end }.to raise_error ArgumentError, /#{input.inspect} is not a valid rendering format/ end end end end diff --git a/spec/unit/interface/action_manager_spec.rb b/spec/unit/interface/action_manager_spec.rb index 3a84e4f83..5df891f74 100755 --- a/spec/unit/interface/action_manager_spec.rb +++ b/spec/unit/interface/action_manager_spec.rb @@ -1,282 +1,282 @@ #!/usr/bin/env rspec require 'spec_helper' # This is entirely an internal class for Interface, so we have to load it instead of our class. require 'puppet/interface' require 'puppet/face' class ActionManagerTester include Puppet::Interface::ActionManager end describe Puppet::Interface::ActionManager do subject { ActionManagerTester.new } describe "when included in a class" do it "should be able to define an action" do subject.action(:foo) do when_invoked { |options| "something "} end end it "should be able to define a 'script' style action" do subject.script :bar do |options| "a bar is where beer is found" end end it "should be able to list defined actions" do subject.action(:foo) do when_invoked { |options| "something" } end subject.action(:bar) do when_invoked { |options| "something" } end subject.actions.should =~ [:foo, :bar] end it "should list 'script' actions" do subject.script :foo do |options| "foo" end subject.actions.should =~ [:foo] end it "should list both script and normal actions" do subject.action :foo do when_invoked do |options| "foo" end end subject.script :bar do |options| "a bar is where beer is found" end subject.actions.should =~ [:foo, :bar] end it "should be able to indicate when an action is defined" do subject.action(:foo) do when_invoked { |options| "something" } end subject.should be_action(:foo) end it "should indicate an action is defined for script actions" do subject.script :foo do |options| "foo" end subject.should be_action :foo end it "should correctly treat action names specified as strings" do subject.action(:foo) do when_invoked { |options| "something" } end subject.should be_action("foo") end end describe "when used to extend a class" do subject { Class.new.extend(Puppet::Interface::ActionManager) } it "should be able to define an action" do subject.action(:foo) do when_invoked { |options| "something "} end end it "should be able to list defined actions" do subject.action(:foo) do when_invoked { |options| "something" } end subject.action(:bar) do when_invoked { |options| "something" } end subject.actions.should include(:bar) subject.actions.should include(:foo) end it "should be able to indicate when an action is defined" do subject.action(:foo) { when_invoked do |options| true end } subject.should be_action(:foo) end end describe "when used both at the class and instance level" do before do @klass = Class.new do include Puppet::Interface::ActionManager extend Puppet::Interface::ActionManager def __invoke_decorations(*args) true end def options() [] end end @instance = @klass.new end it "should be able to define an action at the class level" do @klass.action(:foo) do when_invoked { |options| "something "} end end it "should create an instance method when an action is defined at the class level" do @klass.action(:foo) do when_invoked { |options| "something" } end @instance.foo.should == "something" end it "should be able to define an action at the instance level" do @instance.action(:foo) do when_invoked { |options| "something "} end end it "should create an instance method when an action is defined at the instance level" do @instance.action(:foo) do when_invoked { |options| "something" } end @instance.foo.should == "something" end it "should be able to list actions defined at the class level" do @klass.action(:foo) do when_invoked { |options| "something" } end @klass.action(:bar) do when_invoked { |options| "something" } end @klass.actions.should include(:bar) @klass.actions.should include(:foo) end it "should be able to list actions defined at the instance level" do @instance.action(:foo) do when_invoked { |options| "something" } end @instance.action(:bar) do when_invoked { |options| "something" } end @instance.actions.should include(:bar) @instance.actions.should include(:foo) end it "should be able to list actions defined at both instance and class level" do @klass.action(:foo) do when_invoked { |options| "something" } end @instance.action(:bar) do when_invoked { |options| "something" } end @instance.actions.should include(:bar) @instance.actions.should include(:foo) end it "should be able to indicate when an action is defined at the class level" do @klass.action(:foo) do when_invoked { |options| "something" } end @instance.should be_action(:foo) end it "should be able to indicate when an action is defined at the instance level" do @klass.action(:foo) do when_invoked { |options| "something" } end @instance.should be_action(:foo) end context "with actions defined in superclass" do before :each do @subclass = Class.new(@klass) @instance = @subclass.new @klass.action(:parent) do when_invoked { |options| "a" } end @subclass.action(:sub) do when_invoked { |options| "a" } end @instance.action(:instance) do when_invoked { |options| "a" } end end it "should list actions defined in superclasses" do @instance.should be_action(:parent) @instance.should be_action(:sub) @instance.should be_action(:instance) end it "should list inherited actions" do @instance.actions.should =~ [:instance, :parent, :sub] end it "should not duplicate instance actions after fetching them (#7699)" do @instance.actions.should =~ [:instance, :parent, :sub] @instance.get_action(:instance) @instance.actions.should =~ [:instance, :parent, :sub] end it "should not duplicate subclass actions after fetching them (#7699)" do @instance.actions.should =~ [:instance, :parent, :sub] @instance.get_action(:sub) @instance.actions.should =~ [:instance, :parent, :sub] end it "should not duplicate superclass actions after fetching them (#7699)" do @instance.actions.should =~ [:instance, :parent, :sub] @instance.get_action(:parent) @instance.actions.should =~ [:instance, :parent, :sub] end end it "should create an instance method when an action is defined in a superclass" do @subclass = Class.new(@klass) @instance = @subclass.new @klass.action(:foo) do when_invoked { |options| "something" } end @instance.foo.should == "something" end end describe "#action" do it 'should add an action' do subject.action(:foo) { when_invoked do |options| true end } subject.get_action(:foo).should be_a Puppet::Interface::Action end it 'should support default actions' do subject.action(:foo) { when_invoked do |options| true end; default } subject.get_default_action.should == subject.get_action(:foo) end it 'should not support more than one default action' do subject.action(:foo) { when_invoked do |options| true end; default } expect { subject.action(:bar) { when_invoked do |options| true end default } - }.should raise_error /cannot both be default/ + }.to raise_error /cannot both be default/ end end describe "#get_action" do let :parent_class do parent_class = Class.new(Puppet::Interface) parent_class.action(:foo) { when_invoked do |options| true end } parent_class end it "should check that we can find inherited actions when we are a class" do Class.new(parent_class).get_action(:foo).name.should == :foo end it "should check that we can find inherited actions when we are an instance" do instance = parent_class.new(:foo, '0.0.0') instance.get_action(:foo).name.should == :foo end end end diff --git a/spec/unit/interface/action_spec.rb b/spec/unit/interface/action_spec.rb index 6b68eb149..0d8c62195 100755 --- a/spec/unit/interface/action_spec.rb +++ b/spec/unit/interface/action_spec.rb @@ -1,649 +1,650 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/interface/action' describe Puppet::Interface::Action do describe "when validating the action name" do [nil, '', 'foo bar', '-foobar'].each do |input| it "should treat #{input.inspect} as an invalid name" do - expect { Puppet::Interface::Action.new(nil, input) }. - should raise_error(/is an invalid action name/) + expect { + Puppet::Interface::Action.new(nil, input) + }.to raise_error(/is an invalid action name/) end end end describe "#when_invoked=" do it "should fail if the block has arity 0" do pending "Ruby 1.8 (painfully) treats argument-free blocks as arity -1" if RUBY_VERSION =~ /^1\.8/ expect { Puppet::Interface.new(:action_when_invoked, '1.0.0') do action :foo do when_invoked { } end end }.to raise_error ArgumentError, /foo/ end it "should work with arity 1 blocks" do face = Puppet::Interface.new(:action_when_invoked, '1.0.0') do action :foo do when_invoked {|one| } end end # -1, because we use option defaulting. :( face.method(:foo).arity.should == -1 end it "should work with arity 2 blocks" do face = Puppet::Interface.new(:action_when_invoked, '1.0.0') do action :foo do when_invoked {|one, two| } end end # -2, because we use option defaulting. :( face.method(:foo).arity.should == -2 end it "should work with arity 1 blocks that collect arguments" do face = Puppet::Interface.new(:action_when_invoked, '1.0.0') do action :foo do when_invoked {|*one| } end end # -1, because we use only varargs face.method(:foo).arity.should == -1 end it "should work with arity 2 blocks that collect arguments" do face = Puppet::Interface.new(:action_when_invoked, '1.0.0') do action :foo do when_invoked {|one, *two| } end end # -2, because we take one mandatory argument, and one varargs face.method(:foo).arity.should == -2 end end describe "when invoking" do it "should be able to call other actions on the same object" do face = Puppet::Interface.new(:my_face, '0.0.1') do action(:foo) do when_invoked { |options| 25 } end action(:bar) do when_invoked { |options| "the value of foo is '#{foo}'" } end end face.foo.should == 25 face.bar.should == "the value of foo is '25'" end # bar is a class action calling a class action # quux is a class action calling an instance action # baz is an instance action calling a class action # qux is an instance action calling an instance action it "should be able to call other actions on the same object when defined on a class" do class Puppet::Interface::MyInterfaceBaseClass < Puppet::Interface action(:foo) do when_invoked { |options| 25 } end action(:bar) do when_invoked { |options| "the value of foo is '#{foo}'" } end action(:quux) do when_invoked { |options| "qux told me #{qux}" } end end face = Puppet::Interface::MyInterfaceBaseClass.new(:my_inherited_face, '0.0.1') do action(:baz) do when_invoked { |options| "the value of foo in baz is '#{foo}'" } end action(:qux) do when_invoked { |options| baz } end end face.foo.should == 25 face.bar.should == "the value of foo is '25'" face.quux.should == "qux told me the value of foo in baz is '25'" face.baz.should == "the value of foo in baz is '25'" face.qux.should == "the value of foo in baz is '25'" end context "when calling the Ruby API" do let :face do Puppet::Interface.new(:ruby_api, '1.0.0') do action :bar do option "--bar" when_invoked do |*args| args.last end end end end it "should work when no options are supplied" do options = face.bar options.should == {} end it "should work when options are supplied" do options = face.bar(:bar => "beer") options.should == { :bar => "beer" } end it "should call #validate_and_clean on the action when invoked" do face.get_action(:bar).expects(:validate_and_clean).with({}).returns({}) face.bar 1, :two, 'three' end end end describe "with action-level options" do it "should support options with an empty block" do face = Puppet::Interface.new(:action_level_options, '0.0.1') do action :foo do when_invoked do |options| true end option "--bar" do # this line left deliberately blank end end end face.should_not be_option :bar face.get_action(:foo).should be_option :bar end it "should return only action level options when there are no face options" do face = Puppet::Interface.new(:action_level_options, '0.0.1') do action :foo do when_invoked do |options| true end option "--bar" end end face.get_action(:foo).options.should =~ [:bar] end describe "option aliases" do let :option do action.get_option :bar end let :action do face.get_action :foo end let :face do Puppet::Interface.new(:action_level_options, '0.0.1') do action :foo do when_invoked do |options| options end option "--bar", "--foo", "-b" end end end it "should only list options and not aliases" do action.options.should =~ [:bar] end it "should use the canonical option name when passed aliases" do name = option.name option.aliases.each do |input| face.foo(input => 1).should == { name => 1 } end end end describe "with both face and action options" do let :face do Puppet::Interface.new(:action_level_options, '0.0.1') do action :foo do when_invoked do |options| true end ; option "--bar" end action :baz do when_invoked do |options| true end ; option "--bim" end option "--quux" end end it "should return combined face and action options" do face.get_action(:foo).options.should =~ [:bar, :quux] end it "should fetch options that the face inherited" do parent = Class.new(Puppet::Interface) parent.option "--foo" child = parent.new(:inherited_options, '0.0.1') do option "--bar" action :action do when_invoked do |options| true end option "--baz" end end action = child.get_action(:action) action.should be [:baz, :bar, :foo].each do |name| action.get_option(name).should be_an_instance_of Puppet::Interface::Option end end it "should get an action option when asked" do face.get_action(:foo).get_option(:bar). should be_an_instance_of Puppet::Interface::Option end it "should get a face option when asked" do face.get_action(:foo).get_option(:quux). should be_an_instance_of Puppet::Interface::Option end it "should return options only for this action" do face.get_action(:baz).options.should =~ [:bim, :quux] end end it_should_behave_like "things that declare options" do def add_options_to(&block) face = Puppet::Interface.new(:with_options, '0.0.1') do action(:foo) do when_invoked do |options| true end self.instance_eval &block end end face.get_action(:foo) end end it "should fail when a face option duplicates an action option" do expect { Puppet::Interface.new(:action_level_options, '0.0.1') do option "--foo" action :bar do option "--foo" end end - }.should raise_error ArgumentError, /Option foo conflicts with existing option foo/i + }.to raise_error ArgumentError, /Option foo conflicts with existing option foo/i end it "should fail when a required action option is not provided" do face = Puppet::Interface.new(:required_action_option, '0.0.1') do action(:bar) do option('--foo') { required } when_invoked {|options| } end end expect { face.bar }.to raise_error ArgumentError, /The following options are required: foo/ end it "should fail when a required face option is not provided" do face = Puppet::Interface.new(:required_face_option, '0.0.1') do option('--foo') { required } action(:bar) { when_invoked {|options| } } end expect { face.bar }.to raise_error ArgumentError, /The following options are required: foo/ end end context "with decorators" do context "declared locally" do let :face do Puppet::Interface.new(:action_decorators, '0.0.1') do action :bar do when_invoked do |options| true end end def reported; @reported; end def report(arg) (@reported ||= []) << arg end end end it "should execute before advice on action options in declaration order" do face.action(:boo) do option("--foo") { before_action { |_,_,_| report :foo } } option("--bar", '-b') { before_action { |_,_,_| report :bar } } option("-q", "--quux") { before_action { |_,_,_| report :quux } } option("-f") { before_action { |_,_,_| report :f } } option("--baz") { before_action { |_,_,_| report :baz } } when_invoked {|options| } end face.boo :foo => 1, :bar => 1, :quux => 1, :f => 1, :baz => 1 face.reported.should == [ :foo, :bar, :quux, :f, :baz ] end it "should execute after advice on action options in declaration order" do face.action(:boo) do option("--foo") { after_action { |_,_,_| report :foo } } option("--bar", '-b') { after_action { |_,_,_| report :bar } } option("-q", "--quux") { after_action { |_,_,_| report :quux } } option("-f") { after_action { |_,_,_| report :f } } option("--baz") { after_action { |_,_,_| report :baz } } when_invoked {|options| } end face.boo :foo => 1, :bar => 1, :quux => 1, :f => 1, :baz => 1 face.reported.should == [ :foo, :bar, :quux, :f, :baz ].reverse end it "should execute before advice on face options in declaration order" do face.instance_eval do option("--foo") { before_action { |_,_,_| report :foo } } option("--bar", '-b') { before_action { |_,_,_| report :bar } } option("-q", "--quux") { before_action { |_,_,_| report :quux } } option("-f") { before_action { |_,_,_| report :f } } option("--baz") { before_action { |_,_,_| report :baz } } end face.script(:boo) {|options| } face.boo :foo => 1, :bar => 1, :quux => 1, :f => 1, :baz => 1 face.reported.should == [ :foo, :bar, :quux, :f, :baz ] end it "should execute after advice on face options in declaration order" do face.instance_eval do option("--foo") { after_action { |_,_,_| report :foo } } option("--bar", '-b') { after_action { |_,_,_| report :bar } } option("-q", "--quux") { after_action { |_,_,_| report :quux } } option("-f") { after_action { |_,_,_| report :f } } option("--baz") { after_action { |_,_,_| report :baz } } end face.script(:boo) {|options| } face.boo :foo => 1, :bar => 1, :quux => 1, :f => 1, :baz => 1 face.reported.should == [ :foo, :bar, :quux, :f, :baz ].reverse end it "should execute before advice on face options before action options" do face.instance_eval do option("--face-foo") { before_action { |_,_,_| report :face_foo } } option("--face-bar", '-r') { before_action { |_,_,_| report :face_bar } } action(:boo) do option("--action-foo") { before_action { |_,_,_| report :action_foo } } option("--action-bar", '-b') { before_action { |_,_,_| report :action_bar } } option("-q", "--action-quux") { before_action { |_,_,_| report :action_quux } } option("-a") { before_action { |_,_,_| report :a } } option("--action-baz") { before_action { |_,_,_| report :action_baz } } when_invoked {|options| } end option("-u", "--face-quux") { before_action { |_,_,_| report :face_quux } } option("-f") { before_action { |_,_,_| report :f } } option("--face-baz") { before_action { |_,_,_| report :face_baz } } end expected_calls = [ :face_foo, :face_bar, :face_quux, :f, :face_baz, :action_foo, :action_bar, :action_quux, :a, :action_baz ] face.boo Hash[ *expected_calls.zip([]).flatten ] face.reported.should == expected_calls end it "should execute after advice on face options in declaration order" do face.instance_eval do option("--face-foo") { after_action { |_,_,_| report :face_foo } } option("--face-bar", '-r') { after_action { |_,_,_| report :face_bar } } action(:boo) do option("--action-foo") { after_action { |_,_,_| report :action_foo } } option("--action-bar", '-b') { after_action { |_,_,_| report :action_bar } } option("-q", "--action-quux") { after_action { |_,_,_| report :action_quux } } option("-a") { after_action { |_,_,_| report :a } } option("--action-baz") { after_action { |_,_,_| report :action_baz } } when_invoked {|options| } end option("-u", "--face-quux") { after_action { |_,_,_| report :face_quux } } option("-f") { after_action { |_,_,_| report :f } } option("--face-baz") { after_action { |_,_,_| report :face_baz } } end expected_calls = [ :face_foo, :face_bar, :face_quux, :f, :face_baz, :action_foo, :action_bar, :action_quux, :a, :action_baz ] face.boo Hash[ *expected_calls.zip([]).flatten ] face.reported.should == expected_calls.reverse end it "should not invoke a decorator if the options are empty" do face.option("--foo FOO") { before_action { |_,_,_| report :before_action } } face.expects(:report).never face.bar end context "passing a subset of the options" do before :each do face.option("--foo") { before_action { |_,_,_| report :foo } } face.option("--bar") { before_action { |_,_,_| report :bar } } end it "should invoke only foo's advice when passed only 'foo'" do face.bar(:foo => true) face.reported.should == [ :foo ] end it "should invoke only bar's advice when passed only 'bar'" do face.bar(:bar => true) face.reported.should == [ :bar ] end it "should invoke advice for all passed options" do face.bar(:foo => true, :bar => true) face.reported.should == [ :foo, :bar ] end end end context "and inheritance" do let :parent do Class.new(Puppet::Interface) do script(:on_parent) {|options| :on_parent } def reported; @reported; end def report(arg) (@reported ||= []) << arg end end end let :child do parent.new(:inherited_decorators, '0.0.1') do script(:on_child) {|options| :on_child } end end context "locally declared face options" do subject do child.option("--foo=") { before_action { |_,_,_| report :child_before } } child end it "should be invoked when calling a child action" do subject.on_child(:foo => true).should == :on_child subject.reported.should == [ :child_before ] end it "should be invoked when calling a parent action" do subject.on_parent(:foo => true).should == :on_parent subject.reported.should == [ :child_before ] end end context "inherited face option decorators" do subject do parent.option("--foo=") { before_action { |_,_,_| report :parent_before } } child end it "should be invoked when calling a child action" do subject.on_child(:foo => true).should == :on_child subject.reported.should == [ :parent_before ] end it "should be invoked when calling a parent action" do subject.on_parent(:foo => true).should == :on_parent subject.reported.should == [ :parent_before ] end end context "with both inherited and local face options" do # Decorations should be invoked in declaration order, according to # inheritance (e.g. parent class options should be handled before # subclass options). subject do child.option "-c" do before_action { |action, args, options| report :c_before } after_action { |action, args, options| report :c_after } end parent.option "-a" do before_action { |action, args, options| report :a_before } after_action { |action, args, options| report :a_after } end child.option "-d" do before_action { |action, args, options| report :d_before } after_action { |action, args, options| report :d_after } end parent.option "-b" do before_action { |action, args, options| report :b_before } after_action { |action, args, options| report :b_after } end child.script(:decorations) { |options| report :invoked } child end it "should invoke all decorations when calling a child action" do subject.decorations(:a => 1, :b => 1, :c => 1, :d => 1) subject.reported.should == [ :a_before, :b_before, :c_before, :d_before, :invoked, :d_after, :c_after, :b_after, :a_after ] end it "should invoke all decorations when calling a parent action" do subject.decorations(:a => 1, :b => 1, :c => 1, :d => 1) subject.reported.should == [ :a_before, :b_before, :c_before, :d_before, :invoked, :d_after, :c_after, :b_after, :a_after ] end end end end it_should_behave_like "documentation on faces" do subject do face = Puppet::Interface.new(:action_documentation, '0.0.1') do action :documentation do when_invoked do |options| true end end end face.get_action(:documentation) end end context "#when_rendering" do it "should fail if no type is given when_rendering" it "should accept a when_rendering block" it "should accept multiple when_rendering blocks" it "should fail if when_rendering gets a non-symbol identifier" it "should fail if a second block is given for the same type" it "should return the block if asked" end context "#validate_and_clean" do subject do Puppet::Interface.new(:validate_args, '1.0.0') do script :test do |options| options end end end it "should fail if a required option is not passed" do subject.option "--foo" do required end expect { subject.test }.to raise_error ArgumentError, /options are required/ end it "should fail if two aliases to one option are passed" do subject.option "--foo", "-f" expect { subject.test :foo => true, :f => true }. to raise_error ArgumentError, /Multiple aliases for the same option/ end it "should fail if an unknown option is passed" do expect { subject.test :unknown => true }. to raise_error ArgumentError, /Unknown options passed: unknown/ end it "should report all the unknown options passed" do expect { subject.test :unknown => true, :unseen => false }. to raise_error ArgumentError, /Unknown options passed: unknown, unseen/ end it "should accept 'global' options from settings" do expect { subject.test(:certname => "true").should == { :certname => "true" } }.not_to raise_error end end context "default option values" do subject do Puppet::Interface.new(:default_option_values, '1.0.0') do action :foo do option "--foo" do end option "--bar" do end when_invoked do |options| options end end end end let :action do subject.get_action :foo end let :option do action.get_option :foo end it "should not add options without defaults" do subject.foo.should == {} end it "should not add options without defaults, if options are given" do subject.foo(:bar => 1).should == { :bar => 1 } end it "should add the option default value when set" do option.default = proc { 12 } subject.foo.should == { :foo => 12 } end it "should add the option default value when set, if other options are given" do option.default = proc { 12 } subject.foo(:bar => 1).should == { :foo => 12, :bar => 1 } end it "should invoke the same default proc every time called" do option.default = proc { @foo ||= {} } subject.foo[:foo].object_id.should == subject.foo[:foo].object_id end [nil, 0, 1, true, false, {}, []].each do |input| it "should not override a passed option (#{input.inspect})" do option.default = proc { :fail } subject.foo(:foo => input).should == { :foo => input } end end end context "runtime manipulations" do subject do Puppet::Interface.new(:runtime_manipulations, '1.0.0') do action :foo do when_invoked do |options| options end end end end let :action do subject.get_action :foo end it "should be the face default action if default is set true" do subject.get_default_action.should be_nil action.default = true subject.get_default_action.should == action end end end diff --git a/spec/unit/interface/face_collection_spec.rb b/spec/unit/interface/face_collection_spec.rb index 514a624b1..ca31f4096 100755 --- a/spec/unit/interface/face_collection_spec.rb +++ b/spec/unit/interface/face_collection_spec.rb @@ -1,190 +1,190 @@ #!/usr/bin/env rspec require 'spec_helper' require 'tmpdir' require 'puppet/interface/face_collection' describe Puppet::Interface::FaceCollection do # To avoid cross-pollution we have to save and restore both the hash # containing all the interface data, and the array used by require. Restoring # both means that we don't leak side-effects across the code. --daniel 2011-04-06 # # Worse luck, we *also* need to flush $" of anything defining a face, # because otherwise we can cross-pollute from other test files and end up # with no faces loaded, but the require value set true. --daniel 2011-04-10 before :each do @original_faces = subject.instance_variable_get("@faces").dup @original_required = $".dup $".delete_if do |path| path =~ %r{/face/.*\.rb$} end subject.instance_variable_get(:@faces).clear subject.instance_variable_set(:@loaded, false) end after :each do subject.instance_variable_set(:@faces, @original_faces) @original_required.each {|f| $".push f unless $".include? f } end describe "::[]" do before :each do subject.instance_variable_get("@faces")[:foo][SemVer.new('0.0.1')] = 10 end it "should return the face with the given name" do subject["foo", '0.0.1'].should == 10 end it "should attempt to load the face if it isn't found" do subject.expects(:require).once.with('puppet/face/bar') subject.expects(:require).once.with('puppet/face/0.0.1/bar') subject["bar", '0.0.1'] end it "should attempt to load the default face for the specified version :current" do subject.expects(:require).with('puppet/face/fozzie') subject['fozzie', :current] end it "should return true if the face specified is registered" do subject.instance_variable_get("@faces")[:foo][SemVer.new('0.0.1')] = 10 subject["foo", '0.0.1'].should == 10 end it "should attempt to require the face if it is not registered" do subject.expects(:require).with do |file| subject.instance_variable_get("@faces")[:bar][SemVer.new('0.0.1')] = true file == 'puppet/face/bar' end subject["bar", '0.0.1'].should be_true end it "should return false if the face is not registered" do subject.stubs(:require).returns(true) subject["bar", '0.0.1'].should be_false end it "should return false if the face file itself is missing" do subject.stubs(:require). raises(LoadError, 'no such file to load -- puppet/face/bar').then. raises(LoadError, 'no such file to load -- puppet/face/0.0.1/bar') subject["bar", '0.0.1'].should be_false end it "should register the version loaded by `:current` as `:current`" do subject.expects(:require).with do |file| subject.instance_variable_get("@faces")[:huzzah]['2.0.1'] = :huzzah_face file == 'puppet/face/huzzah' end subject["huzzah", :current] subject.instance_variable_get("@faces")[:huzzah][:current].should == :huzzah_face end context "with something on disk" do it "should register the version loaded from `puppet/face/{name}` as `:current`" do subject["huzzah", '2.0.1'].should be subject["huzzah", :current].should be Puppet::Face[:huzzah, '2.0.1'].should == Puppet::Face[:huzzah, :current] end it "should index :current when the code was pre-required" do subject.instance_variable_get("@faces")[:huzzah].should_not be_key :current require 'puppet/face/huzzah' subject[:huzzah, :current].should be_true end end it "should not cause an invalid face to be enumerated later" do subject[:there_is_no_face, :current].should be_false subject.faces.should_not include :there_is_no_face end end describe "::get_action_for_face" do it "should return an action on the current face" do Puppet::Face::FaceCollection.get_action_for_face(:huzzah, :bar, :current). should be_an_instance_of Puppet::Interface::Action end it "should return an action on an older version of a face" do action = Puppet::Face::FaceCollection. get_action_for_face(:huzzah, :obsolete, :current) action.should be_an_instance_of Puppet::Interface::Action action.face.version.should == SemVer.new('1.0.0') end it "should load the full older version of a face" do action = Puppet::Face::FaceCollection. get_action_for_face(:huzzah, :obsolete, :current) action.face.version.should == SemVer.new('1.0.0') action.face.should be_action :obsolete_in_core end it "should not add obsolete actions to the current version" do action = Puppet::Face::FaceCollection. get_action_for_face(:huzzah, :obsolete, :current) action.face.version.should == SemVer.new('1.0.0') action.face.should be_action :obsolete_in_core current = Puppet::Face[:huzzah, :current] current.version.should == SemVer.new('2.0.1') current.should_not be_action :obsolete_in_core current.should_not be_action :obsolete end end describe "::register" do it "should store the face by name" do face = Puppet::Face.new(:my_face, '0.0.1') subject.register(face) subject.instance_variable_get("@faces").should == { :my_face => { face.version => face } } end end describe "::underscorize" do faulty = [1, "#foo", "$bar", "sturm und drang", :"sturm und drang"] valid = { "Foo" => :foo, :Foo => :foo, "foo_bar" => :foo_bar, :foo_bar => :foo_bar, "foo-bar" => :foo_bar, :"foo-bar" => :foo_bar, } valid.each do |input, expect| it "should map #{input.inspect} to #{expect.inspect}" do result = subject.underscorize(input) result.should == expect end end faulty.each do |input| it "should fail when presented with #{input.inspect} (#{input.class})" do expect { subject.underscorize(input) }. - should raise_error ArgumentError, /not a valid face name/ + to raise_error ArgumentError, /not a valid face name/ end end end context "faulty faces" do before :each do $:.unshift "#{PuppetSpec::FIXTURE_DIR}/faulty_face" end after :each do $:.delete_if {|x| x == "#{PuppetSpec::FIXTURE_DIR}/faulty_face"} end it "should not die if a face has a syntax error" do subject.faces.should be_include :help subject.faces.should_not be_include :syntax @logs.should_not be_empty @logs.first.message.should =~ /syntax error/ end end end diff --git a/spec/unit/interface/option_spec.rb b/spec/unit/interface/option_spec.rb index e73561fba..8afef52e6 100755 --- a/spec/unit/interface/option_spec.rb +++ b/spec/unit/interface/option_spec.rb @@ -1,144 +1,148 @@ require 'puppet/interface' require 'puppet/interface/option' describe Puppet::Interface::Option do let :face do Puppet::Interface.new(:option_testing, '0.0.1') end describe "#optparse_to_name" do ["", "=BAR", " BAR", "=bar", " bar"].each do |postfix| { "--foo" => :foo, "-f" => :f }.each do |base, expect| input = base + postfix it "should map #{input.inspect} to #{expect.inspect}" do option = Puppet::Interface::Option.new(face, input) option.name.should == expect end end end [:foo, 12, nil, {}, []].each do |input| it "should fail sensible when given #{input.inspect}" do - expect { Puppet::Interface::Option.new(face, input) }. - should raise_error ArgumentError, /is not valid for an option argument/ + expect { + Puppet::Interface::Option.new(face, input) + }.to raise_error ArgumentError, /is not valid for an option argument/ end end ["-foo", "-foo=BAR", "-foo BAR"].each do |input| it "should fail with a single dash for long option #{input.inspect}" do - expect { Puppet::Interface::Option.new(face, input) }. - should raise_error ArgumentError, /long options need two dashes \(--\)/ + expect { + Puppet::Interface::Option.new(face, input) + }.to raise_error ArgumentError, /long options need two dashes \(--\)/ end end end it "requires a face when created" do - expect { Puppet::Interface::Option.new }. - should raise_error ArgumentError, /wrong number of arguments/ + expect { + Puppet::Interface::Option.new + }.to raise_error ArgumentError, /wrong number of arguments/ end it "also requires some declaration arguments when created" do - expect { Puppet::Interface::Option.new(face) }. - should raise_error ArgumentError, /No option declarations found/ + expect { + Puppet::Interface::Option.new(face) + }.to raise_error ArgumentError, /No option declarations found/ end it "should infer the name from an optparse string" do option = Puppet::Interface::Option.new(face, "--foo") option.name.should == :foo end it "should infer the name when multiple optparse string are given" do option = Puppet::Interface::Option.new(face, "--foo", "-f") option.name.should == :foo end it "should prefer the first long option name over a short option name" do option = Puppet::Interface::Option.new(face, "-f", "--foo") option.name.should == :foo end it "should create an instance when given a face and name" do Puppet::Interface::Option.new(face, "--foo"). should be_instance_of Puppet::Interface::Option end describe "#to_s" do it "should transform a symbol into a string" do option = Puppet::Interface::Option.new(face, "--foo") option.name.should == :foo option.to_s.should == "foo" end it "should use - rather than _ to separate words in strings but not symbols" do option = Puppet::Interface::Option.new(face, "--foo-bar") option.name.should == :foo_bar option.to_s.should == "foo-bar" end end %w{before after}.each do |side| describe "#{side} hooks" do subject { Puppet::Interface::Option.new(face, "--foo") } let :proc do Proc.new do :from_proc end end it { should respond_to "#{side}_action" } it { should respond_to "#{side}_action=" } it "should set the #{side}_action hook" do subject.send("#{side}_action").should be_nil subject.send("#{side}_action=", proc) subject.send("#{side}_action").should be_an_instance_of UnboundMethod end data = [1, "foo", :foo, Object.new, method(:hash), method(:hash).unbind] data.each do |input| it "should fail if a #{input.class} is added to the #{side} hooks" do expect { subject.send("#{side}_action=", input) }. to raise_error ArgumentError, /not a proc/ end end end end context "defaults" do subject { Puppet::Interface::Option.new(face, "--foo") } it "should work sanely if member variables are used for state" do subject.default = proc { @foo ||= 0; @foo += 1 } subject.default.should == 1 subject.default.should == 2 subject.default.should == 3 end context "with no default" do it { should_not be_has_default } its :default do should be_nil end it "should set a proc as default" do - expect { subject.default = proc { 12 } }.should_not raise_error + expect { subject.default = proc { 12 } }.to_not raise_error end [1, {}, [], Object.new, "foo"].each do |input| it "should reject anything but a proc (#{input.class})" do expect { subject.default = input }.to raise_error ArgumentError, /not a proc/ end end end context "with a default" do before :each do subject.default = proc { [:foo] } end it { should be_has_default } its :default do should == [:foo] end it "should invoke the block every time" do subject.default.object_id.should_not == subject.default.object_id subject.default.should == subject.default end it "should allow replacing the default proc" do subject.default.should == [:foo] subject.default = proc { :bar } subject.default.should == :bar end end end end diff --git a/spec/unit/interface_spec.rb b/spec/unit/interface_spec.rb index 4ff71ac3d..1b4f70ec3 100755 --- a/spec/unit/interface_spec.rb +++ b/spec/unit/interface_spec.rb @@ -1,233 +1,233 @@ require 'spec_helper' require 'puppet/face' require 'puppet/interface' describe Puppet::Interface do subject { Puppet::Interface } before :each do @faces = Puppet::Interface::FaceCollection. instance_variable_get("@faces").dup @dq = $".dup $".delete_if do |path| path =~ %r{/face/.*\.rb$} end Puppet::Interface::FaceCollection.instance_variable_get("@faces").clear end after :each do Puppet::Interface::FaceCollection.instance_variable_set("@faces", @faces) $".clear ; @dq.each do |item| $" << item end end describe "#[]" do it "should fail when no version is requested" do - expect { subject[:huzzah] }.should raise_error ArgumentError + expect { subject[:huzzah] }.to raise_error ArgumentError end it "should raise an exception when the requested version is unavailable" do - expect { subject[:huzzah, '17.0.0'] }.should raise_error, Puppet::Error + expect { subject[:huzzah, '17.0.0'] }.to raise_error, Puppet::Error end it "should raise an exception when the requested face doesn't exist" do - expect { subject[:burrble_toot, :current] }.should raise_error, Puppet::Error + expect { subject[:burrble_toot, :current] }.to raise_error, Puppet::Error end describe "version matching" do { '1' => '1.1.1', '1.0' => '1.0.1', '1.0.1' => '1.0.1', '1.1' => '1.1.1', '1.1.1' => '1.1.1' }.each do |input, expect| it "should match #{input.inspect} to #{expect.inspect}" do face = subject[:version_matching, input] face.should be face.version.should == expect end end %w{1.0.2 1.2}.each do |input| it "should not match #{input.inspect} to any version" do expect { subject[:version_matching, input] }. to raise_error Puppet::Error, /Could not find version/ end end end end describe "#define" do it "should register the face" do face = subject.define(:face_test_register, '0.0.1') face.should == subject[:face_test_register, '0.0.1'] end it "should load actions" do subject.any_instance.expects(:load_actions) subject.define(:face_test_load_actions, '0.0.1') end it "should require a version number" do expect { subject.define(:no_version) }.to raise_error ArgumentError end it "should support summary builder and accessor methods" do subject.new(:foo, '1.0.0').should respond_to(:summary).with(0).arguments subject.new(:foo, '1.0.0').should respond_to(:summary=).with(1).arguments end # Required documentation methods... { :summary => "summary", :description => "This is the description of the stuff\n\nWhee", :examples => "This is my example", :short_description => "This is my custom short description", :notes => "These are my notes...", :author => "This is my authorship data", }.each do |attr, value| it "should support #{attr} in the builder" do face = subject.new(:builder, '1.0.0') do self.send(attr, value) end face.send(attr).should == value end end end describe "#initialize" do it "should require a version number" do expect { subject.new(:no_version) }.to raise_error ArgumentError end it "should require a valid version number" do expect { subject.new(:bad_version, 'Rasins') }. - should raise_error ArgumentError + to raise_error ArgumentError end it "should instance-eval any provided block", :'fails_on_ruby_1.9.2' => true do face = subject.new(:face_test_block, '0.0.1') do action(:something) do when_invoked { "foo" } end end face.something.should == "foo" end end it "should have a name" do subject.new(:me, '0.0.1').name.should == :me end it "should stringify with its own name" do subject.new(:me, '0.0.1').to_s.should =~ /\bme\b/ end # Why? it "should create a class-level autoloader" do subject.autoloader.should be_instance_of(Puppet::Util::Autoload) end it "should try to require faces that are not known" do subject::FaceCollection.expects(:load_face).with(:foo, :current) subject::FaceCollection.expects(:load_face).with(:foo, '0.0.1') expect { subject[:foo, '0.0.1'] }.to raise_error Puppet::Error end it_should_behave_like "things that declare options" do def add_options_to(&block) subject.new(:with_options, '0.0.1', &block) end end describe "with face-level options", :'fails_on_ruby_1.9.2' => true do it "should not return any action-level options" do face = subject.new(:with_options, '0.0.1') do option "--foo" option "--bar" action :baz do when_invoked { true } option "--quux" end end face.options.should =~ [:foo, :bar] end it "should fail when a face option duplicates an action option" do expect { subject.new(:action_level_options, '0.0.1') do action :bar do when_invoked { true } option "--foo" end option "--foo" end - }.should raise_error ArgumentError, /Option foo conflicts with existing option foo on/i + }.to raise_error ArgumentError, /Option foo conflicts with existing option foo on/i end it "should work when two actions have the same option" do face = subject.new(:with_options, '0.0.1') do action :foo do when_invoked { true } ; option "--quux" end action :bar do when_invoked { true } ; option "--quux" end end face.get_action(:foo).options.should =~ [:quux] face.get_action(:bar).options.should =~ [:quux] end it "should only list options and not aliases" do face = subject.new(:face_options, '0.0.1') do option "--bar", "-b", "--foo-bar" end face.options.should =~ [:bar] end end describe "with inherited options" do let :parent do parent = Class.new(subject) parent.option("--inherited") parent.action(:parent_action) do when_invoked { true } end parent end let :face do face = parent.new(:example, '0.2.1') face.option("--local") face.action(:face_action) do when_invoked { true } end face end describe "#options", :'fails_on_ruby_1.9.2' => true do it "should list inherited options" do face.options.should =~ [:inherited, :local] end it "should see all options on face actions" do face.get_action(:face_action).options.should =~ [:inherited, :local] end it "should see all options on inherited actions accessed on the subclass" do face.get_action(:parent_action).options.should =~ [:inherited, :local] end it "should not see subclass actions on the parent class" do parent.options.should =~ [:inherited] end it "should not see subclass actions on actions accessed on the parent class" do parent.get_action(:parent_action).options.should =~ [:inherited] end end describe "#get_option", :'fails_on_ruby_1.9.2' => true do it "should return an inherited option object" do face.get_option(:inherited).should be_an_instance_of subject::Option end end end it_should_behave_like "documentation on faces" do subject do Puppet::Interface.new(:face_documentation, '0.0.1') end end end diff --git a/spec/unit/network/handler/fileserver_spec.rb b/spec/unit/network/handler/fileserver_spec.rb index 2b8094b8b..9c0683136 100755 --- a/spec/unit/network/handler/fileserver_spec.rb +++ b/spec/unit/network/handler/fileserver_spec.rb @@ -1,201 +1,201 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/network/handler/fileserver' describe Puppet::Network::Handler::FileServer do include PuppetSpec::Files def create_file(filename) File.open(filename, "w") { |f| f.puts filename} end def create_nested_file dirname = File.join(@basedir, "nested_dir") Dir.mkdir(dirname) file = File.join(dirname, "nested_dir_file") create_file(file) end before do @basedir = tmpdir("test_network_handler") @file = File.join(@basedir, "aFile") @link = File.join(@basedir, "aLink") create_file(@file) @mount = Puppet::Network::Handler::FileServer::Mount.new("some_path", @basedir) end describe "when parsing the fileserver.conf" do it "should create a valid mount when a valid conf is read" do config_file = tmpfile('fileserver.conf') mountdir = tmpdir('mountdir') conf_text = <<-HEREDOC [mymount] path #{mountdir} allow anyone.com deny nobody.com HEREDOC File.open(config_file, 'w') { |f| f.write conf_text } fs = Puppet::Network::Handler::FileServer.new(:Config => config_file) mounts = fs.instance_variable_get(:@mounts) mount = mounts["mymount"] mount.path == mountdir mount.instance_variable_get(:@declarations).map {|d| d.pattern}.should =~ [["com", "nobody"], ["com", "anyone"]] end ['path', 'allow', 'deny'].each do |arg| it "should error if config file doesn't specify a mount for #{arg} argument" do config_file = tmpfile('fileserver.conf') File.open(config_file, 'w') { |f| f.puts "#{arg} 127.0.0.1/24" } - expect { - Puppet::Network::Handler::FileServer.new(:Config => config_file) - }.should raise_error(Puppet::Network::Handler::FileServerError, "No mount specified for argument #{arg} 127.0.0.1/24") + expect { + Puppet::Network::Handler::FileServer.new(:Config => config_file) + }.to raise_error(Puppet::Network::Handler::FileServerError, "No mount specified for argument #{arg} 127.0.0.1/24") end end end it "should list a single directory" do @mount.list("/", false, false).should == [["/", "directory"]] end it "should list a file within a directory when given the file path" do @mount.list("/aFile", false, "false").should == [["/", "file"]] end it "should list a file within a directory when given the file path with recursion" do @mount.list("/aFile", true, "false").should == [["/", "file"]] end it "should return nil for a non-existent path" do @mount.list("/no_such_file", false, false).should be(nil) end it "should list a symbolic link as a file when given the link path", :unless => Puppet.features.microsoft_windows? do File.symlink(@file, @link) @mount.list("/aLink", false, false).should == [["/", "file"]] end it "should return nil for a dangling symbolic link when given the link path", :unless => Puppet.features.microsoft_windows? do File.symlink("/some/where", @link) @mount.list("/aLink", false, false).should be(nil) end it "should list directory contents of a flat directory structure when asked to recurse" do list = @mount.list("/", true, false) list.should include(["/aFile", "file"]) list.should include(["/", "directory"]) list.should have(2).items end it "should list the contents of a nested directory" do create_nested_file list = @mount.list("/", true, false) list.sort.should == [ ["/aFile", "file"], ["/", "directory"] , ["/nested_dir", "directory"], ["/nested_dir/nested_dir_file", "file"]].sort end it "should list the contents of a directory ignoring files that match" do create_nested_file list = @mount.list("/", true, "*File") list.sort.should == [ ["/", "directory"] , ["/nested_dir", "directory"], ["/nested_dir/nested_dir_file", "file"]].sort end it "should list the contents of a directory ignoring directories that match" do create_nested_file list = @mount.list("/", true, "*nested_dir") list.sort.should == [ ["/aFile", "file"], ["/", "directory"] ].sort end it "should list the contents of a directory ignoring all ignore patterns that match" do create_nested_file list = @mount.list("/", true, ["*File" , "*nested_dir"]) list.should == [ ["/", "directory"] ] end it "should list the directory when recursing to a depth of zero" do create_nested_file list = @mount.list("/", 0, false) list.should == [["/", "directory"]] end it "should list the base directory and files and nested directory to a depth of one" do create_nested_file list = @mount.list("/", 1, false) list.sort.should == [ ["/aFile", "file"], ["/nested_dir", "directory"], ["/", "directory"] ].sort end it "should list the base directory and files and nested directory to a depth of two" do create_nested_file list = @mount.list("/", 2, false) list.sort.should == [ ["/aFile", "file"], ["/", "directory"] , ["/nested_dir", "directory"], ["/nested_dir/nested_dir_file", "file"]].sort end it "should list the base directory and files and nested directory to a depth greater than the directory structure" do create_nested_file list = @mount.list("/", 42, false) list.sort.should == [ ["/aFile", "file"], ["/", "directory"] , ["/nested_dir", "directory"], ["/nested_dir/nested_dir_file", "file"]].sort end it "should list a valid symbolic link as a file when recursing base dir", :unless => Puppet.features.microsoft_windows? do File.symlink(@file, @link) list = @mount.list("/", true, false) list.sort.should == [ ["/", "directory"], ["/aFile", "file"], ["/aLink", "file"] ].sort end it "should not error when a dangling symlink is present", :unless => Puppet.features.microsoft_windows? do File.symlink("/some/where", @link) lambda { @mount.list("/", true, false) }.should_not raise_error end it "should return the directory contents of valid entries when a dangling symlink is present", :unless => Puppet.features.microsoft_windows? do File.symlink("/some/where", @link) list = @mount.list("/", true, false) list.sort.should == [ ["/aFile", "file"], ["/", "directory"] ].sort end describe Puppet::Network::Handler::FileServer::PluginMount, :'fails_on_ruby_1.9.2' => true do PLUGINS = Puppet::Network::Handler::FileServer::PLUGINS # create a module plugin hierarchy def create_plugin(mod, plugin) dirname = File.join(@basedir, mod) Dir.mkdir(dirname) plugins = File.join(dirname, PLUGINS) Dir.mkdir(plugins) facter = File.join(plugins, plugin) Dir.mkdir(facter) create_file(File.join(facter,"fact.rb")) end before :each do @modules = ["one","two"] @modules.each { |m| create_plugin(m, "facter") } Puppet::Node::Environment.new.stubs(:modulepath).returns @basedir @mount = Puppet::Network::Handler::FileServer::PluginMount.new(PLUGINS) @mount.allow("*") end it "should list a file within a directory when given the file path with recursion" do @mount.list("facter/fact.rb", true, "false").should == [["/", "file"], ["/", "file"]] end it "should return a merged view of all plugins for all modules" do list = @mount.list("facter",true,false) list.should == [["/", "directory"], ["/fact.rb", "file"], ["/", "directory"], ["/fact.rb", "file"]] end it "should not fail for inexistant plugins type" do @mount.list("puppet/parser",true,false) end end after do FileUtils.rm_rf(@basedir) end end diff --git a/spec/unit/parser/ast/resource_spec.rb b/spec/unit/parser/ast/resource_spec.rb index 68ad9c229..609bc73f9 100755 --- a/spec/unit/parser/ast/resource_spec.rb +++ b/spec/unit/parser/ast/resource_spec.rb @@ -1,183 +1,210 @@ #!/usr/bin/env rspec require 'spec_helper' describe Puppet::Parser::AST::Resource do ast = Puppet::Parser::AST describe "for builtin types" do before :each do @title = Puppet::Parser::AST::String.new(:value => "mytitle") @compiler = Puppet::Parser::Compiler.new(Puppet::Node.new("mynode")) @scope = Puppet::Parser::Scope.new(:compiler => @compiler) @scope.stubs(:resource).returns(stub_everything) @instance = ast::ResourceInstance.new(:title => @title, :parameters => ast::ASTArray.new(:children => [])) @resource = ast::Resource.new(:type => "file", :instances => ast::ASTArray.new(:children => [@instance])) @resource.stubs(:qualified_type).returns("Resource") end it "should evaluate all its parameters" do param = stub 'param' param.expects(:safeevaluate).with(@scope).returns Puppet::Parser::Resource::Param.new(:name => "myparam", :value => "myvalue", :source => stub("source")) @instance.stubs(:parameters).returns [param] @resource.evaluate(@scope) end it "should evaluate its title" do @resource.evaluate(@scope)[0].title.should == "mytitle" end it "should flatten the titles array" do titles = [] %w{one two}.each do |title| titles << Puppet::Parser::AST::String.new(:value => title) end array = Puppet::Parser::AST::ASTArray.new(:children => titles) @instance.title = array result = @resource.evaluate(@scope).collect { |r| r.title } result.should be_include("one") result.should be_include("two") end + + [5, 5.0, true, false, :three, "foo"].each do |title| + it "should stringify the title if it is a #{title.class}" do + @instance.title.stubs(:safeevaluate).returns title + + result = @resource.evaluate(@scope).map(&:title) + result.first.should == title.to_s + end + end + + [ + {:a => 1}, + /noway/i, + Puppet::Resource.new(:notify, "something"), + Pathname.new("foo"), + (1..50), + nil, + ].each do |title| + it "should fail if the title is a #{title.class}" do + @instance.title.stubs(:safeevaluate).returns title + + expect do + @resource.evaluate(@scope) + end.to raise_error(Puppet::ParseError, /Resource title must be a String, not #{title.class}/) + end + end + it "should create and return one resource objects per title" do titles = [] %w{one two}.each do |title| titles << Puppet::Parser::AST::String.new(:value => title) end array = Puppet::Parser::AST::ASTArray.new(:children => titles) @instance.title = array result = @resource.evaluate(@scope).collect { |r| r.title } result.should be_include("one") result.should be_include("two") end it "should implicitly iterate over instances" do new_title = Puppet::Parser::AST::String.new(:value => "other_title") new_instance = ast::ResourceInstance.new(:title => new_title, :parameters => ast::ASTArray.new(:children => [])) @resource.instances.push(new_instance) @resource.evaluate(@scope).collect { |r| r.title }.should == ["mytitle", "other_title"] end it "should handover resources to the compiler" do titles = [] %w{one two}.each do |title| titles << Puppet::Parser::AST::String.new(:value => title) end array = Puppet::Parser::AST::ASTArray.new(:children => titles) @instance.title = array result = @resource.evaluate(@scope) result.each do |res| @compiler.catalog.resource(res.ref).should be_instance_of(Puppet::Parser::Resource) end end it "should generate virtual resources if it is virtual" do @resource.virtual = true result = @resource.evaluate(@scope) result[0].should be_virtual end it "should generate virtual and exported resources if it is exported" do @resource.exported = true result = @resource.evaluate(@scope) result[0].should be_virtual result[0].should be_exported end # Related to #806, make sure resources always look up the full path to the resource. describe "when generating qualified resources" do before do @scope = Puppet::Parser::Scope.new :compiler => Puppet::Parser::Compiler.new(Puppet::Node.new("mynode")) @parser = Puppet::Parser::Parser.new(Puppet::Node::Environment.new) ["one", "one::two", "three"].each do |name| @parser.environment.known_resource_types.add(Puppet::Resource::Type.new(:definition, name, {})) end @twoscope = @scope.newscope(:namespace => "one") @twoscope.resource = @scope.resource end def resource(type, params = nil) params ||= Puppet::Parser::AST::ASTArray.new(:children => []) instance = Puppet::Parser::AST::ResourceInstance.new( :title => Puppet::Parser::AST::String.new(:value => "myresource"), :parameters => params) Puppet::Parser::AST::Resource.new(:type => type, :instances => Puppet::Parser::AST::ASTArray.new(:children => [instance])) end it "should be able to generate resources with fully qualified type information" do resource("two").evaluate(@twoscope)[0].type.should == "One::Two" end it "should be able to generate resources with unqualified type information" do resource("one").evaluate(@twoscope)[0].type.should == "One" end it "should correctly generate resources that can look up builtin types" do resource("file").evaluate(@twoscope)[0].type.should == "File" end it "should correctly generate resources that can look up defined classes by title" do @scope.known_resource_types.add_hostclass Puppet::Resource::Type.new(:hostclass, "Myresource", {}) @scope.compiler.stubs(:evaluate_classes) res = resource("class").evaluate(@twoscope)[0] res.type.should == "Class" res.title.should == "Myresource" end it "should evaluate parameterized classes when they are instantiated" do @scope.known_resource_types.add_hostclass Puppet::Resource::Type.new(:hostclass, "Myresource", {}) @scope.compiler.expects(:evaluate_classes).with(['myresource'],@twoscope,false) resource("class").evaluate(@twoscope)[0] end it "should fail for resource types that do not exist" do lambda { resource("nosuchtype").evaluate(@twoscope) }.should raise_error(Puppet::ParseError) end end end describe "for class resources" do before do @title = Puppet::Parser::AST::String.new(:value => "classname") @compiler = Puppet::Parser::Compiler.new(Puppet::Node.new("mynode")) @scope = Puppet::Parser::Scope.new(:compiler => @compiler) @scope.stubs(:resource).returns(stub_everything) @instance = ast::ResourceInstance.new(:title => @title, :parameters => ast::ASTArray.new(:children => [])) @resource = ast::Resource.new(:type => "Class", :instances => ast::ASTArray.new(:children => [@instance])) @resource.stubs(:qualified_type).returns("Resource") @type = Puppet::Resource::Type.new(:hostclass, "classname") @compiler.known_resource_types.add(@type) end it "should instantiate the class" do @compiler.stubs(:evaluate_classes) result = @resource.evaluate(@scope) result.length.should == 1 result.first.ref.should == "Class[Classname]" @compiler.catalog.resource("Class[Classname]").should equal(result.first) end it "should cause its parent to be evaluated" do parent_type = Puppet::Resource::Type.new(:hostclass, "parentname") @compiler.stubs(:evaluate_classes) @compiler.known_resource_types.add(parent_type) @type.parent = "parentname" result = @resource.evaluate(@scope) result.length.should == 1 result.first.ref.should == "Class[Classname]" @compiler.catalog.resource("Class[Classname]").should equal(result.first) @compiler.catalog.resource("Class[Parentname]").should be_instance_of(Puppet::Parser::Resource) end end end diff --git a/spec/unit/parser/functions/create_resources_spec.rb b/spec/unit/parser/functions/create_resources_spec.rb index 8a1a28f0d..4a54fb4f5 100755 --- a/spec/unit/parser/functions/create_resources_spec.rb +++ b/spec/unit/parser/functions/create_resources_spec.rb @@ -1,203 +1,203 @@ require 'puppet' require 'spec_helper' require 'puppet_spec/compiler' describe 'function for dynamically creating resources' do include PuppetSpec::Compiler before :each do @scope = Puppet::Parser::Scope.new @scope.compiler = Puppet::Parser::Compiler.new(Puppet::Node.new("floppy", :environment => 'production')) @topscope = @scope.compiler.topscope @compiler = @scope.compiler @scope.parent = @topscope Puppet::Parser::Functions.function(:create_resources) end it "should exist" do Puppet::Parser::Functions.function(:create_resources).should == "function_create_resources" end it 'should require two or three arguments' do - expect { @scope.function_create_resources(['foo']) }.should raise_error(ArgumentError, 'create_resources(): wrong number of arguments (1; must be 2 or 3)') - expect { @scope.function_create_resources(['foo', 'bar', 'blah', 'baz']) }.should raise_error(ArgumentError, 'create_resources(): wrong number of arguments (4; must be 2 or 3)') + expect { @scope.function_create_resources(['foo']) }.to raise_error(ArgumentError, 'create_resources(): wrong number of arguments (1; must be 2 or 3)') + expect { @scope.function_create_resources(['foo', 'bar', 'blah', 'baz']) }.to raise_error(ArgumentError, 'create_resources(): wrong number of arguments (4; must be 2 or 3)') end describe 'when the caller does not supply a name parameter' do it 'should set a default resource name equal to the resource title' do Puppet::Parser::Resource.any_instance.expects(:set_parameter).with(:name, 'test').once @scope.function_create_resources(['notify', {'test'=>{}}]) end end describe 'when the caller supplies a name parameter' do it 'should set the resource name to the value provided' do Puppet::Parser::Resource.any_instance.expects(:set_parameter).with(:name, 'user_supplied').once Puppet::Parser::Resource.any_instance.expects(:set_parameter).with(:name, 'test').never @scope.function_create_resources(['notify', {'test'=>{'name' => 'user_supplied'}}]) end end describe 'when creating native types' do it 'empty hash should not cause resources to be added' do noop_catalog = compile_to_catalog("create_resources('file', {})") empty_catalog = compile_to_catalog("") noop_catalog.resources.size.should == empty_catalog.resources.size end it 'should be able to add' do catalog = compile_to_catalog("create_resources('file', {'/etc/foo'=>{'ensure'=>'present'}})") catalog.resource(:file, "/etc/foo")['ensure'].should == 'present' end it 'should accept multiple types' do catalog = compile_to_catalog("create_resources('notify', {'foo'=>{'message'=>'one'}, 'bar'=>{'message'=>'two'}})") catalog.resource(:notify, "foo")['message'].should == 'one' catalog.resource(:notify, "bar")['message'].should == 'two' end it 'should fail to add non-existing type' do - expect { @scope.function_create_resources(['create-resource-foo', {}]) }.should raise_error(ArgumentError, 'could not create resource of unknown type create-resource-foo') + expect { @scope.function_create_resources(['create-resource-foo', {}]) }.to raise_error(ArgumentError, 'could not create resource of unknown type create-resource-foo') end it 'should be able to add edges' do catalog = compile_to_catalog("notify { test: }\n create_resources('notify', {'foo'=>{'require'=>'Notify[test]'}})") rg = catalog.to_ral.relationship_graph test = rg.vertices.find { |v| v.title == 'test' } foo = rg.vertices.find { |v| v.title == 'foo' } test.must be foo.must be rg.path_between(test,foo).should be end it 'should account for default values' do catalog = compile_to_catalog("create_resources('file', {'/etc/foo'=>{'ensure'=>'present'}, '/etc/baz'=>{'group'=>'food'}}, {'group' => 'bar'})") catalog.resource(:file, "/etc/foo")['group'].should == 'bar' catalog.resource(:file, "/etc/baz")['group'].should == 'food' end end describe 'when dynamically creating resource types' do it 'should be able to create defined resoure types' do catalog = compile_to_catalog(<<-MANIFEST) define foocreateresource($one) { notify { $name: message => $one } } create_resources('foocreateresource', {'blah'=>{'one'=>'two'}}) MANIFEST catalog.resource(:notify, "blah")['message'].should == 'two' end it 'should fail if defines are missing params' do - expect { + expect { compile_to_catalog(<<-MANIFEST) define foocreateresource($one) { notify { $name: message => $one } } create_resources('foocreateresource', {'blah'=>{}}) MANIFEST - }.should raise_error(Puppet::Error, 'Must pass one to Foocreateresource[blah] at line 1 on node foonode') + }.to raise_error(Puppet::Error, 'Must pass one to Foocreateresource[blah] at line 1 on node foonode') end it 'should be able to add multiple defines' do catalog = compile_to_catalog(<<-MANIFEST) define foocreateresource($one) { notify { $name: message => $one } } create_resources('foocreateresource', {'blah'=>{'one'=>'two'}, 'blaz'=>{'one'=>'three'}}) MANIFEST catalog.resource(:notify, "blah")['message'].should == 'two' catalog.resource(:notify, "blaz")['message'].should == 'three' end it 'should be able to add edges' do catalog = compile_to_catalog(<<-MANIFEST) define foocreateresource($one) { notify { $name: message => $one } } notify { test: } create_resources('foocreateresource', {'blah'=>{'one'=>'two', 'require' => 'Notify[test]'}}) MANIFEST rg = catalog.to_ral.relationship_graph test = rg.vertices.find { |v| v.title == 'test' } blah = rg.vertices.find { |v| v.title == 'blah' } test.must be blah.must be rg.path_between(test,blah).should be catalog.resource(:notify, "blah")['message'].should == 'two' end it 'should account for default values' do catalog = compile_to_catalog(<<-MANIFEST) define foocreateresource($one) { notify { $name: message => $one } } create_resources('foocreateresource', {'blah'=>{}}, {'one' => 'two'}) MANIFEST catalog.resource(:notify, "blah")['message'].should == 'two' end end describe 'when creating classes' do it 'should be able to create classes' do catalog = compile_to_catalog(<<-MANIFEST) class bar($one) { notify { test: message => $one } } create_resources('class', {'bar'=>{'one'=>'two'}}) MANIFEST catalog.resource(:notify, "test")['message'].should == 'two' catalog.resource(:class, "bar").should_not be_nil end it 'should fail to create non-existing classes' do expect { compile_to_catalog(<<-MANIFEST) create_resources('class', {'blah'=>{'one'=>'two'}}) MANIFEST - }.should raise_error(Puppet::Error ,'could not find hostclass blah at line 1 on node foonode') + }.to raise_error(Puppet::Error ,'could not find hostclass blah at line 1 on node foonode') end it 'should be able to add edges' do catalog = compile_to_catalog(<<-MANIFEST) class bar($one) { notify { test: message => $one } } notify { tester: } create_resources('class', {'bar'=>{'one'=>'two', 'require' => 'Notify[tester]'}}) MANIFEST rg = catalog.to_ral.relationship_graph test = rg.vertices.find { |v| v.title == 'test' } tester = rg.vertices.find { |v| v.title == 'tester' } test.must be tester.must be rg.path_between(tester,test).should be end it 'should account for default values' do catalog = compile_to_catalog(<<-MANIFEST) class bar($one) { notify { test: message => $one } } create_resources('class', {'bar'=>{}}, {'one' => 'two'}) MANIFEST catalog.resource(:notify, "test")['message'].should == 'two' catalog.resource(:class, "bar").should_not be_nil end end end diff --git a/spec/unit/parser/lexer_spec.rb b/spec/unit/parser/lexer_spec.rb index 3dcae64b2..fbe2cd020 100755 --- a/spec/unit/parser/lexer_spec.rb +++ b/spec/unit/parser/lexer_spec.rb @@ -1,742 +1,742 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/parser/lexer' # This is a special matcher to match easily lexer output RSpec::Matchers.define :be_like do |*expected| match do |actual| expected.zip(actual).all? { |e,a| !e or a[0] == e or (e.is_a? Array and a[0] == e[0] and (a[1] == e[1] or (a[1].is_a?(Hash) and a[1][:value] == e[1]))) } end end __ = nil describe Puppet::Parser::Lexer do describe "when reading strings" do before { @lexer = Puppet::Parser::Lexer.new } it "should increment the line count for every carriage return in the string" do @lexer.line = 10 @lexer.string = "this\nis\natest'" @lexer.slurpstring("'") @lexer.line.should == 12 end it "should not increment the line count for escapes in the string" do @lexer.line = 10 @lexer.string = "this\\nis\\natest'" @lexer.slurpstring("'") @lexer.line.should == 10 end it "should not think the terminator is escaped, when preceeded by an even number of backslashes" do @lexer.line = 10 @lexer.string = "here\nis\nthe\nstring\\\\'with\nextra\njunk" @lexer.slurpstring("'") @lexer.line.should == 13 end end end describe Puppet::Parser::Lexer::Token do before do @token = Puppet::Parser::Lexer::Token.new(%r{something}, :NAME) end [:regex, :name, :string, :skip, :incr_line, :skip_text, :accumulate].each do |param| it "should have a #{param.to_s} reader" do @token.should be_respond_to(param) end it "should have a #{param.to_s} writer" do @token.should be_respond_to(param.to_s + "=") end end end describe Puppet::Parser::Lexer::Token, "when initializing" do it "should create a regex if the first argument is a string" do Puppet::Parser::Lexer::Token.new("something", :NAME).regex.should == %r{something} end it "should set the string if the first argument is one" do Puppet::Parser::Lexer::Token.new("something", :NAME).string.should == "something" end it "should set the regex if the first argument is one" do Puppet::Parser::Lexer::Token.new(%r{something}, :NAME).regex.should == %r{something} end end describe Puppet::Parser::Lexer::TokenList do before do @list = Puppet::Parser::Lexer::TokenList.new end it "should have a method for retrieving tokens by the name" do token = @list.add_token :name, "whatever" @list[:name].should equal(token) end it "should have a method for retrieving string tokens by the string" do token = @list.add_token :name, "whatever" @list.lookup("whatever").should equal(token) end it "should add tokens to the list when directed" do token = @list.add_token :name, "whatever" @list[:name].should equal(token) end it "should have a method for adding multiple tokens at once" do @list.add_tokens "whatever" => :name, "foo" => :bar @list[:name].should_not be_nil @list[:bar].should_not be_nil end it "should fail to add tokens sharing a name with an existing token" do @list.add_token :name, "whatever" - lambda { @list.add_token :name, "whatever" }.should raise_error(ArgumentError) + expect { @list.add_token :name, "whatever" }.to raise_error(ArgumentError) end it "should set provided options on tokens being added" do token = @list.add_token :name, "whatever", :skip_text => true token.skip_text.should == true end it "should define any provided blocks as a :convert method" do token = @list.add_token(:name, "whatever") do "foo" end token.convert.should == "foo" end it "should store all string tokens in the :string_tokens list" do one = @list.add_token(:name, "1") @list.string_tokens.should be_include(one) end it "should store all regex tokens in the :regex_tokens list" do one = @list.add_token(:name, %r{one}) @list.regex_tokens.should be_include(one) end it "should not store string tokens in the :regex_tokens list" do one = @list.add_token(:name, "1") @list.regex_tokens.should_not be_include(one) end it "should not store regex tokens in the :string_tokens list" do one = @list.add_token(:name, %r{one}) @list.string_tokens.should_not be_include(one) end it "should sort the string tokens inversely by length when asked" do one = @list.add_token(:name, "1") two = @list.add_token(:other, "12") @list.sort_tokens @list.string_tokens.should == [two, one] end end describe Puppet::Parser::Lexer::TOKENS do before do @lexer = Puppet::Parser::Lexer.new end { :LBRACK => '[', :RBRACK => ']', :LBRACE => '{', :RBRACE => '}', :LPAREN => '(', :RPAREN => ')', :EQUALS => '=', :ISEQUAL => '==', :GREATEREQUAL => '>=', :GREATERTHAN => '>', :LESSTHAN => '<', :LESSEQUAL => '<=', :NOTEQUAL => '!=', :NOT => '!', :COMMA => ',', :DOT => '.', :COLON => ':', :AT => '@', :LLCOLLECT => '<<|', :RRCOLLECT => '|>>', :LCOLLECT => '<|', :RCOLLECT => '|>', :SEMIC => ';', :QMARK => '?', :BACKSLASH => '\\', :FARROW => '=>', :PARROW => '+>', :APPENDS => '+=', :PLUS => '+', :MINUS => '-', :DIV => '/', :TIMES => '*', :LSHIFT => '<<', :RSHIFT => '>>', :MATCH => '=~', :NOMATCH => '!~', :IN_EDGE => '->', :OUT_EDGE => '<-', :IN_EDGE_SUB => '~>', :OUT_EDGE_SUB => '<~', }.each do |name, string| it "should have a token named #{name.to_s}" do Puppet::Parser::Lexer::TOKENS[name].should_not be_nil end it "should match '#{string}' for the token #{name.to_s}" do Puppet::Parser::Lexer::TOKENS[name].string.should == string end end { "case" => :CASE, "class" => :CLASS, "default" => :DEFAULT, "define" => :DEFINE, "import" => :IMPORT, "if" => :IF, "elsif" => :ELSIF, "else" => :ELSE, "inherits" => :INHERITS, "node" => :NODE, "and" => :AND, "or" => :OR, "undef" => :UNDEF, "false" => :FALSE, "true" => :TRUE, "in" => :IN, }.each do |string, name| it "should have a keyword named #{name.to_s}" do Puppet::Parser::Lexer::KEYWORDS[name].should_not be_nil end it "should have the keyword for #{name.to_s} set to #{string}" do Puppet::Parser::Lexer::KEYWORDS[name].string.should == string end end # These tokens' strings don't matter, just that the tokens exist. [:STRING, :DQPRE, :DQMID, :DQPOST, :BOOLEAN, :NAME, :NUMBER, :COMMENT, :MLCOMMENT, :RETURN, :SQUOTE, :DQUOTE, :VARIABLE].each do |name| it "should have a token named #{name.to_s}" do Puppet::Parser::Lexer::TOKENS[name].should_not be_nil end end end describe Puppet::Parser::Lexer::TOKENS[:CLASSREF] do before { @token = Puppet::Parser::Lexer::TOKENS[:CLASSREF] } it "should match against single upper-case alpha-numeric terms" do @token.regex.should =~ "One" end it "should match against upper-case alpha-numeric terms separated by double colons" do @token.regex.should =~ "One::Two" end it "should match against many upper-case alpha-numeric terms separated by double colons" do @token.regex.should =~ "One::Two::Three::Four::Five" end it "should match against upper-case alpha-numeric terms prefixed by double colons" do @token.regex.should =~ "::One" end end describe Puppet::Parser::Lexer::TOKENS[:NAME] do before { @token = Puppet::Parser::Lexer::TOKENS[:NAME] } it "should match against lower-case alpha-numeric terms" do @token.regex.should =~ "one-two" end it "should return itself and the value if the matched term is not a keyword" do Puppet::Parser::Lexer::KEYWORDS.expects(:lookup).returns(nil) @token.convert(stub("lexer"), "myval").should == [Puppet::Parser::Lexer::TOKENS[:NAME], "myval"] end it "should return the keyword token and the value if the matched term is a keyword" do keyword = stub 'keyword', :name => :testing Puppet::Parser::Lexer::KEYWORDS.expects(:lookup).returns(keyword) @token.convert(stub("lexer"), "myval").should == [keyword, "myval"] end it "should return the BOOLEAN token and 'true' if the matched term is the string 'true'" do keyword = stub 'keyword', :name => :TRUE Puppet::Parser::Lexer::KEYWORDS.expects(:lookup).returns(keyword) @token.convert(stub('lexer'), "true").should == [Puppet::Parser::Lexer::TOKENS[:BOOLEAN], true] end it "should return the BOOLEAN token and 'false' if the matched term is the string 'false'" do keyword = stub 'keyword', :name => :FALSE Puppet::Parser::Lexer::KEYWORDS.expects(:lookup).returns(keyword) @token.convert(stub('lexer'), "false").should == [Puppet::Parser::Lexer::TOKENS[:BOOLEAN], false] end it "should match against lower-case alpha-numeric terms separated by double colons" do @token.regex.should =~ "one::two" end it "should match against many lower-case alpha-numeric terms separated by double colons" do @token.regex.should =~ "one::two::three::four::five" end it "should match against lower-case alpha-numeric terms prefixed by double colons" do @token.regex.should =~ "::one" end it "should match against nested terms starting with numbers" do @token.regex.should =~ "::1one::2two::3three" end end describe Puppet::Parser::Lexer::TOKENS[:NUMBER] do before do @token = Puppet::Parser::Lexer::TOKENS[:NUMBER] @regex = @token.regex end it "should match against numeric terms" do @regex.should =~ "2982383139" end it "should match against float terms" do @regex.should =~ "29823.235" end it "should match against hexadecimal terms" do @regex.should =~ "0xBEEF0023" end it "should match against float with exponent terms" do @regex.should =~ "10e23" end it "should match against float terms with negative exponents" do @regex.should =~ "10e-23" end it "should match against float terms with fractional parts and exponent" do @regex.should =~ "1.234e23" end it "should return the NAME token and the value" do @token.convert(stub("lexer"), "myval").should == [Puppet::Parser::Lexer::TOKENS[:NAME], "myval"] end end describe Puppet::Parser::Lexer::TOKENS[:COMMENT] do before { @token = Puppet::Parser::Lexer::TOKENS[:COMMENT] } it "should match against lines starting with '#'" do @token.regex.should =~ "# this is a comment" end it "should be marked to get skipped" do @token.skip?.should be_true end it "should be marked to accumulate" do @token.accumulate?.should be_true end it "'s block should return the comment without the #" do @token.convert(@lexer,"# this is a comment")[1].should == "this is a comment" end end describe Puppet::Parser::Lexer::TOKENS[:MLCOMMENT] do before do @token = Puppet::Parser::Lexer::TOKENS[:MLCOMMENT] @lexer = stub 'lexer', :line => 0 end it "should match against lines enclosed with '/*' and '*/'" do @token.regex.should =~ "/* this is a comment */" end it "should match multiple lines enclosed with '/*' and '*/'" do @token.regex.should =~ """/* this is a comment */""" end it "should increase the lexer current line number by the amount of lines spanned by the comment" do @lexer.expects(:line=).with(2) @token.convert(@lexer, "1\n2\n3") end it "should not greedily match comments" do match = @token.regex.match("/* first */ word /* second */") match[1].should == " first " end it "should be marked to accumulate" do @token.accumulate?.should be_true end it "'s block should return the comment without the comment marks" do @lexer.stubs(:line=).with(0) @token.convert(@lexer,"/* this is a comment */")[1].should == "this is a comment" end end describe Puppet::Parser::Lexer::TOKENS[:RETURN] do before { @token = Puppet::Parser::Lexer::TOKENS[:RETURN] } it "should match against carriage returns" do @token.regex.should =~ "\n" end it "should be marked to initiate text skipping" do @token.skip_text.should be_true end it "should be marked to increment the line" do @token.incr_line.should be_true end end shared_examples_for "variable names in the lexer" do |prefix| # Watch out - a regex might match a *prefix* on these, not just the whole # word, so make sure you don't have false positive or negative results based # on that. legal = %w{f foo f::b foo::b f::bar foo::bar 3 foo3 3foo} illegal = %w{f- f-o -f f::-o f::o- f::o-o} ["", "::"].each do |global_scope| legal.each do |name| var = prefix + global_scope + name it "should accept #{var.inspect} as a valid variable name" do (subject.regex.match(var) || [])[0].should == var end end illegal.each do |name| var = prefix + global_scope + name it "should NOT accept #{var.inspect} as a valid variable name" do (subject.regex.match(var) || [])[0].should_not == var end end end end describe Puppet::Parser::Lexer::TOKENS[:DOLLAR_VAR] do its(:skip_text) { should be_false } its(:incr_line) { should be_false } it_should_behave_like "variable names in the lexer", '$' end describe Puppet::Parser::Lexer::TOKENS[:VARIABLE] do its(:skip_text) { should be_false } its(:incr_line) { should be_false } it_should_behave_like "variable names in the lexer", '' end def tokens_scanned_from(s) lexer = Puppet::Parser::Lexer.new lexer.string = s lexer.fullscan[0..-2] end describe Puppet::Parser::Lexer,"when lexing strings" do { %q{'single quoted string')} => [[:STRING,'single quoted string']], %q{"double quoted string"} => [[:STRING,'double quoted string']], %q{'single quoted string with an escaped "\\'"'} => [[:STRING,'single quoted string with an escaped "\'"']], %q{'single quoted string with an escaped "\$"'} => [[:STRING,'single quoted string with an escaped "\$"']], %q{'single quoted string with an escaped "\."'} => [[:STRING,'single quoted string with an escaped "\."']], %q{'single quoted string with an escaped "\n"'} => [[:STRING,'single quoted string with an escaped "\n"']], %q{'single quoted string with an escaped "\\\\"'} => [[:STRING,'single quoted string with an escaped "\\\\"']], %q{"string with an escaped '\\"'"} => [[:STRING,"string with an escaped '\"'"]], %q{"string with an escaped '\\$'"} => [[:STRING,"string with an escaped '$'"]], %Q{"string with a line ending with a backslash: \\\nfoo"} => [[:STRING,"string with a line ending with a backslash: foo"]], %q{"string with $v (but no braces)"} => [[:DQPRE,"string with "],[:VARIABLE,'v'],[:DQPOST,' (but no braces)']], %q["string with ${v} in braces"] => [[:DQPRE,"string with "],[:VARIABLE,'v'],[:DQPOST,' in braces']], %q["string with ${qualified::var} in braces"] => [[:DQPRE,"string with "],[:VARIABLE,'qualified::var'],[:DQPOST,' in braces']], %q{"string with $v and $v (but no braces)"} => [[:DQPRE,"string with "],[:VARIABLE,"v"],[:DQMID," and "],[:VARIABLE,"v"],[:DQPOST," (but no braces)"]], %q["string with ${v} and ${v} in braces"] => [[:DQPRE,"string with "],[:VARIABLE,"v"],[:DQMID," and "],[:VARIABLE,"v"],[:DQPOST," in braces"]], %q["string with ${'a nested single quoted string'} inside it."] => [[:DQPRE,"string with "],[:STRING,'a nested single quoted string'],[:DQPOST,' inside it.']], %q["string with ${['an array ',$v2]} in it."] => [[:DQPRE,"string with "],:LBRACK,[:STRING,"an array "],:COMMA,[:VARIABLE,"v2"],:RBRACK,[:DQPOST," in it."]], %q{a simple "scanner" test} => [[:NAME,"a"],[:NAME,"simple"], [:STRING,"scanner"],[:NAME,"test"]], %q{a simple 'single quote scanner' test} => [[:NAME,"a"],[:NAME,"simple"], [:STRING,"single quote scanner"],[:NAME,"test"]], %q{a harder 'a $b \c"'} => [[:NAME,"a"],[:NAME,"harder"], [:STRING,'a $b \c"']], %q{a harder "scanner test"} => [[:NAME,"a"],[:NAME,"harder"], [:STRING,"scanner test"]], %q{a hardest "scanner \"test\""} => [[:NAME,"a"],[:NAME,"hardest"],[:STRING,'scanner "test"']], %Q{a hardestest "scanner \\"test\\"\n"} => [[:NAME,"a"],[:NAME,"hardestest"],[:STRING,%Q{scanner "test"\n}]], %q{function("call")} => [[:NAME,"function"],[:LPAREN,"("],[:STRING,'call'],[:RPAREN,")"]], %q["string with ${(3+5)/4} nested math."] => [[:DQPRE,"string with "],:LPAREN,[:NAME,"3"],:PLUS,[:NAME,"5"],:RPAREN,:DIV,[:NAME,"4"],[:DQPOST," nested math."]], %q["$$$$"] => [[:STRING,"$$$$"]], %q["$variable"] => [[:DQPRE,""],[:VARIABLE,"variable"],[:DQPOST,""]], %q["$var$other"] => [[:DQPRE,""],[:VARIABLE,"var"],[:DQMID,""],[:VARIABLE,"other"],[:DQPOST,""]], %q["foo$bar$"] => [[:DQPRE,"foo"],[:VARIABLE,"bar"],[:DQPOST,"$"]], %q["foo$$bar"] => [[:DQPRE,"foo$"],[:VARIABLE,"bar"],[:DQPOST,""]], %q[""] => [[:STRING,""]], %q["123 456 789 0"] => [[:STRING,"123 456 789 0"]], %q["${123} 456 $0"] => [[:DQPRE,""],[:VARIABLE,"123"],[:DQMID," 456 "],[:VARIABLE,"0"],[:DQPOST,""]], %q["$foo::::bar"] => [[:DQPRE,""],[:VARIABLE,"foo"],[:DQPOST,"::::bar"]] }.each { |src,expected_result| it "should handle #{src} correctly" do tokens_scanned_from(src).should be_like(*expected_result) end } end describe Puppet::Parser::Lexer::TOKENS[:DOLLAR_VAR] do before { @token = Puppet::Parser::Lexer::TOKENS[:DOLLAR_VAR] } it "should match against alpha words prefixed with '$'" do @token.regex.should =~ '$this_var' end it "should return the VARIABLE token and the variable name stripped of the '$'" do @token.convert(stub("lexer"), "$myval").should == [Puppet::Parser::Lexer::TOKENS[:VARIABLE], "myval"] end end describe Puppet::Parser::Lexer::TOKENS[:REGEX] do before { @token = Puppet::Parser::Lexer::TOKENS[:REGEX] } it "should match against any expression enclosed in //" do @token.regex.should =~ '/this is a regex/' end it 'should not match if there is \n in the regex' do @token.regex.should_not =~ "/this is \n a regex/" end describe "when scanning" do it "should not consider escaped slashes to be the end of a regex" do tokens_scanned_from("$x =~ /this \\/ foo/").should be_like(__,__,[:REGEX,%r{this / foo}]) end it "should not lex chained division as a regex" do tokens_scanned_from("$x = $a/$b/$c").collect { |name, data| name }.should_not be_include( :REGEX ) end it "should accept a regular expression after NODE" do tokens_scanned_from("node /www.*\.mysite\.org/").should be_like(__,[:REGEX,Regexp.new("www.*\.mysite\.org")]) end it "should accept regular expressions in a CASE" do s = %q{case $variable { "something": {$othervar = 4096 / 2} /regex/: {notice("this notably sucks")} } } tokens_scanned_from(s).should be_like( :CASE,:VARIABLE,:LBRACE,:STRING,:COLON,:LBRACE,:VARIABLE,:EQUALS,:NAME,:DIV,:NAME,:RBRACE,[:REGEX,/regex/],:COLON,:LBRACE,:NAME,:LPAREN,:STRING,:RPAREN,:RBRACE,:RBRACE ) end end it "should return the REGEX token and a Regexp" do @token.convert(stub("lexer"), "/myregex/").should == [Puppet::Parser::Lexer::TOKENS[:REGEX], Regexp.new(/myregex/)] end end describe Puppet::Parser::Lexer, "when lexing comments" do before { @lexer = Puppet::Parser::Lexer.new } it "should accumulate token in munge_token" do token = stub 'token', :skip => true, :accumulate? => true, :incr_line => nil, :skip_text => false token.stubs(:convert).with(@lexer, "# this is a comment").returns([token, " this is a comment"]) @lexer.munge_token(token, "# this is a comment") @lexer.munge_token(token, "# this is a comment") @lexer.getcomment.should == " this is a comment\n this is a comment\n" end it "should add a new comment stack level on LBRACE" do @lexer.string = "{" @lexer.expects(:commentpush) @lexer.fullscan end it "should add a new comment stack level on LPAREN" do @lexer.string = "(" @lexer.expects(:commentpush) @lexer.fullscan end it "should pop the current comment on RPAREN" do @lexer.string = ")" @lexer.expects(:commentpop) @lexer.fullscan end it "should return the current comments on getcomment" do @lexer.string = "# comment" @lexer.fullscan @lexer.getcomment.should == "comment\n" end it "should discard the previous comments on blank line" do @lexer.string = "# 1\n\n# 2" @lexer.fullscan @lexer.getcomment.should == "2\n" end it "should skip whitespace before lexing the next token after a non-token" do tokens_scanned_from("/* 1\n\n */ \ntest").should be_like([:NAME, "test"]) end it "should not return comments seen after the current line" do @lexer.string = "# 1\n\n# 2" @lexer.fullscan @lexer.getcomment(1).should == "" end it "should return a comment seen before the current line" do @lexer.string = "# 1\n# 2" @lexer.fullscan @lexer.getcomment(2).should == "1\n2\n" end end # FIXME: We need to rewrite all of these tests, but I just don't want to take the time right now. describe "Puppet::Parser::Lexer in the old tests" do before { @lexer = Puppet::Parser::Lexer.new } it "should do simple lexing" do { %q{\\} => [[:BACKSLASH,"\\"]], %q{simplest scanner test} => [[:NAME,"simplest"],[:NAME,"scanner"],[:NAME,"test"]], %Q{returned scanner test\n} => [[:NAME,"returned"],[:NAME,"scanner"],[:NAME,"test"]] }.each { |source,expected| tokens_scanned_from(source).should be_like(*expected) } end it "should fail usefully" do - lambda { tokens_scanned_from('^') }.should raise_error(RuntimeError) + expect { tokens_scanned_from('^') }.to raise_error(RuntimeError) end it "should fail if the string is not set" do - lambda { @lexer.fullscan }.should raise_error(Puppet::LexError) + expect { @lexer.fullscan }.to raise_error(Puppet::LexError) end it "should correctly identify keywords" do tokens_scanned_from("case").should be_like([:CASE, "case"]) end it "should correctly parse class references" do %w{Many Different Words A Word}.each { |t| tokens_scanned_from(t).should be_like([:CLASSREF,t])} end # #774 it "should correctly parse namespaced class refernces token" do %w{Foo ::Foo Foo::Bar ::Foo::Bar}.each { |t| tokens_scanned_from(t).should be_like([:CLASSREF, t]) } end it "should correctly parse names" do %w{this is a bunch of names}.each { |t| tokens_scanned_from(t).should be_like([:NAME,t]) } end it "should correctly parse names with numerals" do %w{1name name1 11names names11}.each { |t| tokens_scanned_from(t).should be_like([:NAME,t]) } end it "should correctly parse empty strings" do - lambda { tokens_scanned_from('$var = ""') }.should_not raise_error + expect { tokens_scanned_from('$var = ""') }.to_not raise_error end it "should correctly parse virtual resources" do tokens_scanned_from("@type {").should be_like([:AT, "@"], [:NAME, "type"], [:LBRACE, "{"]) end it "should correctly deal with namespaces" do @lexer.string = %{class myclass} @lexer.fullscan @lexer.namespace.should == "myclass" @lexer.namepop @lexer.namespace.should == "" @lexer.string = "class base { class sub { class more" @lexer.fullscan @lexer.namespace.should == "base::sub::more" @lexer.namepop @lexer.namespace.should == "base::sub" end it "should not put class instantiation on the namespace" do @lexer.string = "class base { class sub { class { mode" @lexer.fullscan @lexer.namespace.should == "base::sub" end it "should correctly handle fully qualified names" do @lexer.string = "class base { class sub::more {" @lexer.fullscan @lexer.namespace.should == "base::sub::more" @lexer.namepop @lexer.namespace.should == "base" end it "should correctly lex variables" do ["$variable", "$::variable", "$qualified::variable", "$further::qualified::variable"].each do |string| tokens_scanned_from(string).should be_like([:VARIABLE,string.sub(/^\$/,'')]) end end it "should end variables at `-`" do tokens_scanned_from('$hyphenated-variable'). should be_like [:VARIABLE, "hyphenated"], [:MINUS, '-'], [:NAME, 'variable'] end it "should not include whitespace in a variable" do tokens_scanned_from("$foo bar").should_not be_like([:VARIABLE, "foo bar"]) end it "should not include excess colons in a variable" do tokens_scanned_from("$foo::::bar").should_not be_like([:VARIABLE, "foo::::bar"]) end end describe "Puppet::Parser::Lexer in the old tests when lexing example files" do my_fixtures('*.pp') do |file| it "should correctly lex #{file}" do lexer = Puppet::Parser::Lexer.new lexer.file = file - expect { lexer.fullscan }.should_not raise_error + expect { lexer.fullscan }.to_not raise_error end end end describe "when trying to lex an non-existent file" do include PuppetSpec::Files it "should return an empty list of tokens" do lexer = Puppet::Parser::Lexer.new lexer.file = nofile = tmpfile('lexer') File.exists?(nofile).should == false lexer.fullscan.should == [[false,false]] end end diff --git a/spec/unit/provider/nameservice/directoryservice_spec.rb b/spec/unit/provider/nameservice/directoryservice_spec.rb index c11388a04..f5f3f4d88 100755 --- a/spec/unit/provider/nameservice/directoryservice_spec.rb +++ b/spec/unit/provider/nameservice/directoryservice_spec.rb @@ -1,209 +1,209 @@ #!/usr/bin/env rspec require 'spec_helper' # We use this as a reasonable way to obtain all the support infrastructure. [:user, :group].each do |type_for_this_round| provider_class = Puppet::Type.type(type_for_this_round).provider(:directoryservice) describe provider_class do before do @resource = stub("resource") @provider = provider_class.new(@resource) end it "[#6009] should handle nested arrays of members" do current = ["foo", "bar", "baz"] desired = ["foo", ["quux"], "qorp"] group = 'example' @resource.stubs(:[]).with(:name).returns(group) @resource.stubs(:[]).with(:auth_membership).returns(true) @provider.instance_variable_set(:@property_value_cache_hash, { :members => current }) %w{bar baz}.each do |del| @provider.expects(:execute).once. with([:dseditgroup, '-o', 'edit', '-n', '.', '-d', del, group]) end %w{quux qorp}.each do |add| @provider.expects(:execute).once. with([:dseditgroup, '-o', 'edit', '-n', '.', '-a', add, group]) end - expect { @provider.set(:members, desired) }.should_not raise_error + expect { @provider.set(:members, desired) }.to_not raise_error end end end describe 'DirectoryService.single_report' do it 'should fail on OS X < 10.4' do Puppet::Provider::NameService::DirectoryService.stubs(:get_macosx_version_major).returns("10.3") - lambda { + expect { Puppet::Provider::NameService::DirectoryService.single_report('resource_name') - }.should raise_error(RuntimeError, "Puppet does not support OS X versions < 10.4") + }.to raise_error(RuntimeError, "Puppet does not support OS X versions < 10.4") end it 'should use url data on 10.4' do Puppet::Provider::NameService::DirectoryService.stubs(:get_macosx_version_major).returns("10.4") Puppet::Provider::NameService::DirectoryService.stubs(:get_ds_path).returns('Users') Puppet::Provider::NameService::DirectoryService.stubs(:list_all_present).returns( ['root', 'user1', 'user2', 'resource_name'] ) Puppet::Provider::NameService::DirectoryService.stubs(:generate_attribute_hash) Puppet::Provider::NameService::DirectoryService.stubs(:execute) Puppet::Provider::NameService::DirectoryService.expects(:parse_dscl_url_data) Puppet::Provider::NameService::DirectoryService.single_report('resource_name') end it 'should use plist data on > 10.4' do Puppet::Provider::NameService::DirectoryService.stubs(:get_macosx_version_major).returns("10.5") Puppet::Provider::NameService::DirectoryService.stubs(:get_ds_path).returns('Users') Puppet::Provider::NameService::DirectoryService.stubs(:list_all_present).returns( ['root', 'user1', 'user2', 'resource_name'] ) Puppet::Provider::NameService::DirectoryService.stubs(:generate_attribute_hash) Puppet::Provider::NameService::DirectoryService.stubs(:execute) Puppet::Provider::NameService::DirectoryService.expects(:parse_dscl_plist_data) Puppet::Provider::NameService::DirectoryService.single_report('resource_name') end end describe 'DirectoryService.get_exec_preamble' do it 'should fail on OS X < 10.4' do Puppet::Provider::NameService::DirectoryService.stubs(:get_macosx_version_major).returns("10.3") - lambda { + expect { Puppet::Provider::NameService::DirectoryService.get_exec_preamble('-list') - }.should raise_error(RuntimeError, "Puppet does not support OS X versions < 10.4") + }.to raise_error(RuntimeError, "Puppet does not support OS X versions < 10.4") end it 'should use url data on 10.4' do Puppet::Provider::NameService::DirectoryService.stubs(:get_macosx_version_major).returns("10.4") Puppet::Provider::NameService::DirectoryService.stubs(:get_ds_path).returns('Users') Puppet::Provider::NameService::DirectoryService.get_exec_preamble('-list').should include("-url") end it 'should use plist data on > 10.4' do Puppet::Provider::NameService::DirectoryService.stubs(:get_macosx_version_major).returns("10.5") Puppet::Provider::NameService::DirectoryService.stubs(:get_ds_path).returns('Users') Puppet::Provider::NameService::DirectoryService.get_exec_preamble('-list').should include("-plist") end end describe 'DirectoryService password behavior' do # The below is a binary plist containing a ShadowHashData key which CONTAINS # another binary plist. The nested binary plist contains a 'SALTED-SHA512' # key that contains a base64 encoded salted-SHA512 password hash... let (:binary_plist) { "bplist00\324\001\002\003\004\005\006\a\bXCRAM-MD5RNT]SALTED-SHA512[RECOVERABLEO\020 \231k2\3360\200GI\201\355J\216\202\215y\243\001\206J\300\363\032\031\022\006\2359\024\257\217<\361O\020\020F\353\at\377\277\226\276c\306\254\031\037J(\235O\020D\335\006{\3744g@\377z\204\322\r\332t\021\330\n\003\246K\223\356\034!P\261\305t\035\346\352p\206\003n\247MMA\310\301Z<\366\246\023\0161W3\340\357\000\317T\t\301\311+\204\246L7\276\370\320*\245O\021\002\000k\024\221\270x\353\001\237\346D}\377?\265]\356+\243\v[\350\316a\340h\376<\322\266\327\016\306n\272r\t\212A\253L\216\214\205\016\241 [\360/\335\002#\\A\372\241a\261\346\346\\\251\330\312\365\016\n\341\017\016\225&;\322\\\004*\ru\316\372\a \362?8\031\247\231\030\030\267\315\023\v\343{@\227\301s\372h\212\000a\244&\231\366\nt\277\2036,\027bZ+\223W\212g\333`\264\331N\306\307\362\257(^~ b\262\247&\231\261t\341\231%\244\247\203eOt\365\271\201\273\330\350\363C^A\327F\214!\217hgf\e\320k\260n\315u~\336\371M\t\235k\230S\375\311\303\240\351\037d\273\321y\335=K\016`_\317\230\2612_\023K\036\350\v\232\323Y\310\317_\035\227%\237\v\340\023\016\243\233\025\306:\227\351\370\364x\234\231\266\367\016w\275\333-\351\210}\375x\034\262\272kRuHa\362T/F!\347B\231O`K\304\037'k$$\245h)e\363\365mT\b\317\\2\361\026\351\254\375Jl1~\r\371\267\352\2322I\341\272\376\243^Un\266E7\230[VocUJ\220N\2116D/\025f=\213\314\325\vG}\311\360\377DT\307m\261&\263\340\272\243_\020\271rG^BW\210\030l\344\0324\335\233\300\023\272\225Im\330\n\227*Yv[\006\315\330y'\a\321\373\273A\240\305F{S\246I#/\355\2425\031\031GGF\270y\n\331\004\023G@\331\000\361\343\350\264$\032\355_\210y\000\205\342\375\212q\024\004\026W:\205 \363v?\035\270L-\270=\022\323\2003\v\336\277\t\237\356\374\n\267n\003\367\342\330;\371S\326\016`B6@Njm>\240\021%\336\345\002(P\204Yn\3279l\0228\264\254\304\2528t\372h\217\347sA\314\345\245\337)]\000\b\000\021\000\032\000\035\000+\0007\000Z\000m\000\264\000\000\000\000\000\000\002\001\000\000\000\000\000\000\000\t\000\000\000\000\000\000\000\000\000\000\000\000\000\000\002\270" } # The below is a base64 encoded salted-SHA512 password hash. let (:pw_string) { "\335\006{\3744g@\377z\204\322\r\332t\021\330\n\003\246K\223\356\034!P\261\305t\035\346\352p\206\003n\247MMA\310\301Z<\366\246\023\0161W3\340\357\000\317T\t\301\311+\204\246L7\276\370\320*\245" } # The below is a salted-SHA512 password hash in hex. let (:sha512_hash) { 'dd067bfc346740ff7a84d20dda7411d80a03a64b93ee1c2150b1c5741de6ea7086036ea74d4d41c8c15a3cf6a6130e315733e0ef00cf5409c1c92b84a64c37bef8d02aa5' } let :plist_path do '/var/db/dslocal/nodes/Default/users/jeff.plist' end let :ds_provider do Puppet::Provider::NameService::DirectoryService end let :shadow_hash_data do {'ShadowHashData' => [StringIO.new(binary_plist)]} end subject do Puppet::Provider::NameService::DirectoryService end before :each do subject.expects(:get_macosx_version_major).returns("10.7") end it 'should execute convert_binary_to_xml once when getting the password on >= 10.7' do subject.expects(:convert_binary_to_xml).returns({'SALTED-SHA512' => StringIO.new(pw_string)}) File.expects(:exists?).with(plist_path).once.returns(true) Plist.expects(:parse_xml).returns(shadow_hash_data) # On Mac OS X 10.7 we first need to convert to xml when reading the password subject.expects(:plutil).with('-convert', 'xml1', '-o', '/dev/stdout', plist_path) subject.get_password('uid', 'jeff') end it 'should fail if a salted-SHA512 password hash is not passed in >= 10.7' do expect { subject.set_password('jeff', 'uid', 'badpassword') - }.should raise_error(RuntimeError, /OS X 10.7 requires a Salted SHA512 hash password of 136 characters./) + }.to raise_error(RuntimeError, /OS X 10.7 requires a Salted SHA512 hash password of 136 characters./) end it 'should convert xml-to-binary and binary-to-xml when setting the pw on >= 10.7' do subject.expects(:convert_binary_to_xml).returns({'SALTED-SHA512' => StringIO.new(pw_string)}) subject.expects(:convert_xml_to_binary).returns(binary_plist) File.expects(:exists?).with(plist_path).once.returns(true) Plist.expects(:parse_xml).returns(shadow_hash_data) # On Mac OS X 10.7 we first need to convert to xml subject.expects(:plutil).with('-convert', 'xml1', '-o', '/dev/stdout', plist_path) # And again back to a binary plist or DirectoryService will complain subject.expects(:plutil).with('-convert', 'binary1', plist_path) Plist::Emit.expects(:save_plist).with(shadow_hash_data, plist_path) subject.set_password('jeff', 'uid', sha512_hash) end it '[#13686] should handle an empty ShadowHashData field in the users plist' do subject.expects(:convert_xml_to_binary).returns(binary_plist) File.expects(:exists?).with(plist_path).once.returns(true) Plist.expects(:parse_xml).returns({'ShadowHashData' => nil}) subject.expects(:plutil).with('-convert', 'xml1', '-o', '/dev/stdout', plist_path) subject.expects(:plutil).with('-convert', 'binary1', plist_path) Plist::Emit.expects(:save_plist) subject.set_password('jeff', 'uid', sha512_hash) end end describe '(#4855) directoryservice group resource failure' do let :provider_class do Puppet::Type.type(:group).provider(:directoryservice) end let :group_members do ['root','jeff'] end let :user_account do ['root'] end let :stub_resource do stub('resource') end subject do provider_class.new(stub_resource) end before :each do @resource = stub("resource") @provider = provider_class.new(@resource) end it 'should delete a group member if the user does not exist' do stub_resource.stubs(:[]).with(:name).returns('fake_group') stub_resource.stubs(:name).returns('fake_group') subject.expects(:execute).with([:dseditgroup, '-o', 'edit', '-n', '.', '-d', 'jeff', 'fake_group']).raises(Puppet::ExecutionFailure, 'it broke') subject.expects(:execute).with([:dscl, '.', '-delete', '/Groups/fake_group', 'GroupMembership', 'jeff']) subject.remove_unwanted_members(group_members, user_account) end end diff --git a/spec/unit/provider/package/freebsd_spec.rb b/spec/unit/provider/package/freebsd_spec.rb index 9c8038791..4fa9aa281 100755 --- a/spec/unit/provider/package/freebsd_spec.rb +++ b/spec/unit/provider/package/freebsd_spec.rb @@ -1,54 +1,54 @@ #!/usr/bin/env rspec require 'spec_helper' provider_class = Puppet::Type.type(:package).provider(:freebsd) describe provider_class do before :each do # Create a mock resource @resource = stub 'resource' # A catch all; no parameters set @resource.stubs(:[]).returns(nil) # But set name and source @resource.stubs(:[]).with(:name).returns "mypackage" @resource.stubs(:[]).with(:ensure).returns :installed @provider = provider_class.new @provider.resource = @resource end it "should have an install method" do @provider = provider_class.new @provider.should respond_to(:install) end describe "when installing" do before :each do @resource.stubs(:should).with(:ensure).returns(:installed) end it "should install a package from a path to a directory" do # For better or worse, trailing '/' is needed. --daniel 2011-01-26 path = '/path/to/directory/' @resource.stubs(:[]).with(:source).returns(path) Puppet::Util::Execution.expects(:withenv).once.with({:PKG_PATH => path}).yields @provider.expects(:pkgadd).once.with("mypackage") - expect { @provider.install }.should_not raise_error + expect { @provider.install }.to_not raise_error end %w{http https ftp}.each do |protocol| it "should install a package via #{protocol}" do # For better or worse, trailing '/' is needed. --daniel 2011-01-26 path = "#{protocol}://localhost/" @resource.stubs(:[]).with(:source).returns(path) Puppet::Util::Execution.expects(:withenv).once.with({:PACKAGESITE => path}).yields @provider.expects(:pkgadd).once.with('-r', "mypackage") - expect { @provider.install }.should_not raise_error + expect { @provider.install }.to_not raise_error end end end end diff --git a/spec/unit/provider/package/pkgdmg_spec.rb b/spec/unit/provider/package/pkgdmg_spec.rb index f8b23167a..3a4f13e0f 100755 --- a/spec/unit/provider/package/pkgdmg_spec.rb +++ b/spec/unit/provider/package/pkgdmg_spec.rb @@ -1,89 +1,89 @@ #!/usr/bin/env rspec require 'spec_helper' describe Puppet::Type.type(:package).provider(:pkgdmg) do let(:resource) { Puppet::Type.type(:package).new(:name => 'foo', :provider => :pkgdmg) } let(:provider) { described_class.new(resource) } it { should_not be_versionable } it { should_not be_uninstallable } describe "when installing it should fail when" do before :each do Puppet::Util.expects(:execute).never end it "no source is specified" do - expect { provider.install }.should raise_error(Puppet::Error, /must specify a package source/) + expect { provider.install }.to raise_error(Puppet::Error, /must specify a package source/) end it "the source does not end in .dmg or .pkg" do resource[:source] = "bar" - expect { provider.install }.should raise_error(Puppet::Error, /must specify a source string ending in .*dmg.*pkg/) + expect { provider.install }.to raise_error(Puppet::Error, /must specify a source string ending in .*dmg.*pkg/) end end # These tests shouldn't be this messy. The pkgdmg provider needs work... describe "when installing a pkgdmg" do let(:fake_mountpoint) { "/tmp/dmg.foo" } let(:empty_hdiutil_plist) { Plist::Emit.dump({}) } let(:fake_hdiutil_plist) { Plist::Emit.dump({"system-entities" => [{"mount-point" => fake_mountpoint}]}) } before do fh = mock 'filehandle' fh.stubs(:path).yields "/tmp/foo" resource[:source] = "foo.dmg" File.stubs(:open).yields fh Dir.stubs(:mktmpdir).returns "/tmp/testtmp123" FileUtils.stubs(:remove_entry_secure) end it "should fail when a disk image with no system entities is mounted" do described_class.stubs(:hdiutil).returns(empty_hdiutil_plist) - expect { provider.install }.should raise_error(Puppet::Error, /No disk entities/) + expect { provider.install }.to raise_error(Puppet::Error, /No disk entities/) end it "should call hdiutil to mount and eject the disk image" do Dir.stubs(:entries).returns [] provider.class.expects(:hdiutil).with("eject", fake_mountpoint).returns 0 provider.class.expects(:hdiutil).with("mount", "-plist", "-nobrowse", "-readonly", "-noidme", "-mountrandom", "/tmp", nil).returns fake_hdiutil_plist provider.install end it "should call installpkg if a pkg/mpkg is found on the dmg" do Dir.stubs(:entries).returns ["foo.pkg"] provider.class.stubs(:hdiutil).returns fake_hdiutil_plist provider.class.expects(:installpkg).with("#{fake_mountpoint}/foo.pkg", resource[:name], "foo.dmg").returns "" provider.install end describe "from a remote source" do let(:tmpdir) { "/tmp/good123" } before :each do resource[:source] = "http://fake.puppetlabs.com/foo.dmg" end it "should call tmpdir and use the returned directory" do Dir.expects(:mktmpdir).returns tmpdir Dir.stubs(:entries).returns ["foo.pkg"] described_class.expects(:curl).with do |*args| args[0] == "-o" and args[1].include? tmpdir end described_class.stubs(:hdiutil).returns fake_hdiutil_plist described_class.expects(:installpkg) provider.install end end end describe "when installing flat pkg file" do it "should call installpkg if a flat pkg file is found instead of a .dmg image" do resource[:source] = "/tmp/test.pkg" resource[:name] = "testpkg" provider.class.expects(:installpkgdmg).with("/tmp/test.pkg", "testpkg").returns "" provider.install end end end diff --git a/spec/unit/provider/service/openrc_spec.rb b/spec/unit/provider/service/openrc_spec.rb new file mode 100755 index 000000000..7dc30deb8 --- /dev/null +++ b/spec/unit/provider/service/openrc_spec.rb @@ -0,0 +1,209 @@ +#!/usr/bin/env rspec + +require 'spec_helper' + +describe Puppet::Type.type(:service).provider(:openrc) do + + before :each do + Puppet::Type.type(:service).stubs(:defaultprovider).returns described_class + ['/sbin/rc-service', '/bin/rc-status', '/sbin/rc-update'].each do |command| + described_class.stubs(:which).with(command).returns(command) + end + end + + describe ".instances" do + + it "should have an instances method" do + described_class.should respond_to :instances + end + + it "should get a list of services from rc-service --list" do + described_class.expects(:rcservice).with('-C','--list').returns File.read(my_fixture('rcservice_list')) + described_class.instances.map(&:name).should == [ + 'alsasound', + 'consolefont', + 'lvm-monitoring', + 'pydoc-2.7', + 'pydoc-3.2', + 'wpa_supplicant', + 'xdm', + 'xdm-setup' + ] + end + end + + describe "#start" do + it "should use the supplied start command if specified" do + provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :start => '/bin/foo')) + provider.expects(:execute).with(['/bin/foo'], :failonfail => true, :squelch => true) + provider.start + end + it "should start the service with rc-service start otherwise" do + provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd')) + provider.expects(:execute).with(['/sbin/rc-service','sshd',:start], :failonfail => true, :squelch => true) + provider.start + end + end + + describe "#stop" do + it "should use the supplied stop command if specified" do + provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :stop => '/bin/foo')) + provider.expects(:execute).with(['/bin/foo'], :failonfail => true, :squelch => true) + provider.stop + end + it "should stop the service with rc-service stop otherwise" do + provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd')) + provider.expects(:execute).with(['/sbin/rc-service','sshd',:stop], :failonfail => true, :squelch => true) + provider.stop + end + end + + describe "#enabled?" do + + before :each do + described_class.any_instance.stubs(:rcstatus).with('-C','-a').returns File.read(my_fixture('rcstatus')) + end + + it "should run rc-status to get a list of enabled services" do + provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd')) + provider.expects(:rcstatus).with('-C','-a').returns "\n" + provider.enabled? + end + + ['hwclock', 'modules', 'urandom'].each do |service| + it "should consider service #{service} in runlevel boot as enabled" do + provider = described_class.new(Puppet::Type.type(:service).new(:name => service)) + provider.enabled?.should == :true + end + end + + ['netmount', 'xdm', 'local', 'foo_with_very_very_long_servicename_no_still_not_the_end_wait_for_it_almost_there_almost_there_now_finally_the_end'].each do |service| + it "should consider service #{service} in runlevel default as enabled" do + provider = described_class.new(Puppet::Type.type(:service).new(:name => service)) + provider.enabled?.should == :true + end + end + + ['net.eth0', 'pcscd'].each do |service| + it "should consider service #{service} in dynamic runlevel: hotplugged as disabled" do + provider = described_class.new(Puppet::Type.type(:service).new(:name => service)) + provider.enabled?.should == :false + end + end + + ['sysfs', 'udev-mount'].each do |service| + it "should consider service #{service} in dynamic runlevel: needed as disabled" do + provider = described_class.new(Puppet::Type.type(:service).new(:name => service)) + provider.enabled?.should == :false + end + end + + ['sshd'].each do |service| + it "should consider service #{service} in dynamic runlevel: manual as disabled" do + provider = described_class.new(Puppet::Type.type(:service).new(:name => service)) + provider.enabled?.should == :false + end + end + + end + + describe "#enable" do + it "should run rc-update add to enable a service" do + provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd')) + provider.expects(:rcupdate).with('-C', :add, 'sshd') + provider.enable + end + end + + describe "#disable" do + it "should run rc-update del to disable a service" do + provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd')) + provider.expects(:rcupdate).with('-C', :del, 'sshd') + provider.disable + end + end + + describe "#status" do + + describe "when a special status command if specified" do + it "should use the status command from the resource" do + provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :status => '/bin/foo')) + provider.expects(:execute).with(['/sbin/rc-service','sshd',:status], :failonfail => false, :squelch => true).never + provider.expects(:execute).with(['/bin/foo'], :failonfail => false, :squelch => true) + provider.status + end + + it "should return :stopped when status command returns with a non-zero exitcode" do + provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :status => '/bin/foo')) + provider.expects(:execute).with(['/sbin/rc-service','sshd',:status], :failonfail => false, :squelch => true).never + provider.expects(:execute).with(['/bin/foo'], :failonfail => false, :squelch => true) + $CHILD_STATUS.stubs(:exitstatus).returns 3 + provider.status.should == :stopped + end + + it "should return :running when status command returns with a zero exitcode" do + provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :status => '/bin/foo')) + provider.expects(:execute).with(['/sbin/rc-service','sshd',:status], :failonfail => false, :squelch => true).never + provider.expects(:execute).with(['/bin/foo'], :failonfail => false, :squelch => true) + $CHILD_STATUS.stubs(:exitstatus).returns 0 + provider.status.should == :running + end + end + + describe "when hasstatus is false" do + it "should return running if a pid can be found" do + provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :hasstatus => false)) + provider.expects(:execute).with(['/sbin/rc-service','sshd',:status], :failonfail => false, :squelch => true).never + provider.expects(:getpid).returns 1000 + provider.status.should == :running + end + + it "should return stopped if no pid can be found" do + provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :hasstatus => false)) + provider.expects(:execute).with(['/sbin/rc-service','sshd',:status], :failonfail => false, :squelch => true).never + provider.expects(:getpid).returns nil + provider.status.should == :stopped + end + end + + describe "when hasstatus is true" do + it "should return running if rc-service status exits with a zero exitcode" do + provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :hasstatus => true)) + provider.expects(:execute).with(['/sbin/rc-service','sshd',:status], :failonfail => false, :squelch => true) + $CHILD_STATUS.stubs(:exitstatus).returns 0 + provider.status.should == :running + end + + it "should return stopped if rc-service status exits with a non-zero exitcode" do + provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :hasstatus => true)) + provider.expects(:execute).with(['/sbin/rc-service','sshd',:status], :failonfail => false, :squelch => true) + $CHILD_STATUS.stubs(:exitstatus).returns 3 + provider.status.should == :stopped + end + end + end + + describe "#restart" do + it "should use the supplied restart command if specified" do + provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :restart => '/bin/foo')) + provider.expects(:execute).with(['/sbin/rc-service','sshd',:restart], :failonfail => true, :squelch => true).never + provider.expects(:execute).with(['/bin/foo'], :failonfail => true, :squelch => true) + provider.restart + end + + it "should restart the service with rc-service restart if hasrestart is true" do + provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :hasrestart => true)) + provider.expects(:execute).with(['/sbin/rc-service','sshd',:restart], :failonfail => true, :squelch => true) + provider.restart + end + + it "should restart the service with rc-service stop/start if hasrestart is false" do + provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :hasrestart => false)) + provider.expects(:execute).with(['/sbin/rc-service','sshd',:restart], :failonfail => true, :squelch => true).never + provider.expects(:execute).with(['/sbin/rc-service','sshd',:stop], :failonfail => true, :squelch => true) + provider.expects(:execute).with(['/sbin/rc-service','sshd',:start], :failonfail => true, :squelch => true) + provider.restart + end + end + +end diff --git a/spec/unit/provider/user/useradd_spec.rb b/spec/unit/provider/user/useradd_spec.rb index 724fc12c0..5aabfb745 100755 --- a/spec/unit/provider/user/useradd_spec.rb +++ b/spec/unit/provider/user/useradd_spec.rb @@ -1,215 +1,222 @@ #!/usr/bin/env rspec require 'spec_helper' provider_class = Puppet::Type.type(:user).provider(:useradd) describe provider_class do before do @resource = stub("resource", :name => "myuser", :managehome? => nil) @resource.stubs(:should).returns "fakeval" @resource.stubs(:[]).returns "fakeval" @provider = provider_class.new(@resource) end # #1360 it "should add -o when allowdupe is enabled and the user is being created" do @resource.expects(:allowdupe?).returns true @resource.expects(:system?).returns true @provider.stubs(:execute) @provider.expects(:execute).with { |args| args.include?("-o") } @provider.create end it "should add -o when allowdupe is enabled and the uid is being modified" do @resource.expects(:allowdupe?).returns true @provider.expects(:execute).with { |args| args.include?("-o") } @provider.uid = 150 end it "should add -r when system is enabled" do @resource.expects(:allowdupe?).returns true @resource.expects(:system?).returns true @provider.stubs(:execute) @provider.expects(:execute).with { |args| args.include?("-r") } @provider.create end it "should set password age rules" do provider_class.has_feature :manages_password_age @resource = Puppet::Type.type(:user).new :name => "myuser", :password_min_age => 5, :password_max_age => 10, :provider => :useradd @provider = provider_class.new(@resource) @provider.stubs(:execute) @provider.expects(:execute).with { |cmd, *args| args == ["-m", 5, "-M", 10, "myuser"] } @provider.create end describe "when checking to add allow dup" do it "should check allow dup" do @resource.expects(:allowdupe?) @provider.check_allow_dup end it "should return an array with a flag if dup is allowed" do @resource.stubs(:allowdupe?).returns true @provider.check_allow_dup.must == ["-o"] end it "should return an empty array if no dup is allowed" do @resource.stubs(:allowdupe?).returns false @provider.check_allow_dup.must == [] end end describe "when checking to add system users" do it "should check system users" do @resource.expects(:system?) @provider.check_system_users end it "should return an array with a flag if it's a system user" do @resource.stubs(:system?).returns true @provider.check_system_users.must == ["-r"] end it "should return an empty array if it's not a system user" do @resource.stubs(:system?).returns false @provider.check_system_users.must == [] end end describe "when checking manage home" do it "should check manage home" do @resource.expects(:managehome?) @provider.check_manage_home end it "should return an array with -m flag if home is managed" do @resource.stubs(:managehome?).returns true @provider.check_manage_home.must == ["-m"] end + + it "should return an array with -r flag if home is managed" do + @resource.stubs(:managehome?).returns true + @resource.stubs(:ensure) == :absent + @provider.stubs(:command).returns("userdel") + @provider.deletecmd.must == ["userdel", "-r", "fakeval"] + end it "should return an array with -M if home is not managed and on Redhat" do Facter.stubs(:value).with("operatingsystem").returns("RedHat") @resource.stubs(:managehome?).returns false @provider.check_manage_home.must == ["-M"] end it "should return an empty array if home is not managed and not on Redhat" do Facter.stubs(:value).with("operatingsystem").returns("some OS") @resource.stubs(:managehome?).returns false @provider.check_manage_home.must == [] end end describe "when adding properties" do it "should get the valid properties" it "should not add the ensure property" it "should add the flag and value to an array" it "should return and array of flags and values" end describe "when calling addcmd" do before do @resource.stubs(:allowdupe?).returns true @resource.stubs(:managehome?).returns true @resource.stubs(:system?).returns true end it "should call command with :add" do @provider.expects(:command).with(:add) @provider.addcmd end it "should add properties" do @provider.expects(:add_properties).returns([]) @provider.addcmd end it "should check and add if dup allowed" do @provider.expects(:check_allow_dup).returns([]) @provider.addcmd end it "should check and add if home is managed" do @provider.expects(:check_manage_home).returns([]) @provider.addcmd end it "should add the resource :name" do @resource.expects(:[]).with(:name) @provider.addcmd end it "should return an array with -r if system? is true" do resource = Puppet::Type.type(:user).new( :name => "bob", :system => true) provider_class.new( resource ).addcmd.should include("-r") end it "should return an array without -r if system? is false" do resource = Puppet::Type.type(:user).new( :name => "bob", :system => false) provider_class.new( resource ).addcmd.should_not include("-r") end it "should return an array with full command" do @provider.stubs(:command).with(:add).returns("useradd") @provider.stubs(:add_properties).returns(["-G", "somegroup"]) @resource.stubs(:[]).with(:name).returns("someuser") @resource.stubs(:[]).with(:expiry).returns("somedate") @provider.addcmd.must == ["useradd", "-G", "somegroup", "-o", "-m", '-e somedate', "-r", "someuser"] end it "should return an array without -e if expiry is undefined full command" do @provider.stubs(:command).with(:add).returns("useradd") @provider.stubs(:add_properties).returns(["-G", "somegroup"]) @resource.stubs(:[]).with(:name).returns("someuser") @resource.stubs(:[]).with(:expiry).returns nil @provider.addcmd.must == ["useradd", "-G", "somegroup", "-o", "-m", "-r", "someuser"] end end describe "when calling passcmd" do before do @resource.stubs(:allowdupe?).returns true @resource.stubs(:managehome?).returns true @resource.stubs(:system?).returns true end it "should call command with :pass" do @provider.expects(:command).with(:password) @provider.passcmd end it "should return nil if neither min nor max is set" do @resource.stubs(:should).with(:password_min_age).returns nil @resource.stubs(:should).with(:password_max_age).returns nil @provider.passcmd.must == nil end it "should return a chage command array with -m and the user name if password_min_age is set" do @provider.stubs(:command).with(:password).returns("chage") @resource.stubs(:[]).with(:name).returns("someuser") @resource.stubs(:should).with(:password_min_age).returns 123 @resource.stubs(:should).with(:password_max_age).returns nil @provider.passcmd.must == ['chage','-m',123,'someuser'] end it "should return a chage command array with -M if password_max_age is set" do @provider.stubs(:command).with(:password).returns("chage") @resource.stubs(:[]).with(:name).returns("someuser") @resource.stubs(:should).with(:password_min_age).returns nil @resource.stubs(:should).with(:password_max_age).returns 999 @provider.passcmd.must == ['chage','-M',999,'someuser'] end it "should return a chage command array with -M -m if both password_min_age and password_max_age are set" do @provider.stubs(:command).with(:password).returns("chage") @resource.stubs(:[]).with(:name).returns("someuser") @resource.stubs(:should).with(:password_min_age).returns 123 @resource.stubs(:should).with(:password_max_age).returns 999 @provider.passcmd.must == ['chage','-m',123,'-M',999,'someuser'] end end end diff --git a/spec/unit/resource_spec.rb b/spec/unit/resource_spec.rb index e8a41e01f..49973e346 100755 --- a/spec/unit/resource_spec.rb +++ b/spec/unit/resource_spec.rb @@ -1,856 +1,856 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/resource' describe Puppet::Resource do include PuppetSpec::Files let :basepath do make_absolute("/somepath") end [:catalog, :file, :line].each do |attr| it "should have an #{attr} attribute" do resource = Puppet::Resource.new("file", "/my/file") resource.should respond_to(attr) resource.should respond_to(attr.to_s + "=") end end it "should have a :title attribute" do Puppet::Resource.new(:user, "foo").title.should == "foo" end it "should require the type and title" do - lambda { Puppet::Resource.new }.should raise_error(ArgumentError) + expect { Puppet::Resource.new }.to raise_error(ArgumentError) end it "should canonize types to capitalized strings" do Puppet::Resource.new(:user, "foo").type.should == "User" end it "should canonize qualified types so all strings are capitalized" do Puppet::Resource.new("foo::bar", "foo").type.should == "Foo::Bar" end it "should tag itself with its type" do Puppet::Resource.new("file", "/f").should be_tagged("file") end it "should tag itself with its title if the title is a valid tag" do Puppet::Resource.new("user", "bar").should be_tagged("bar") end it "should not tag itself with its title if the title is a not valid tag" do Puppet::Resource.new("file", "/bar").should_not be_tagged("/bar") end it "should allow setting of attributes" do Puppet::Resource.new("file", "/bar", :file => "/foo").file.should == "/foo" Puppet::Resource.new("file", "/bar", :exported => true).should be_exported end it "should set its type to 'Class' and its title to the passed title if the passed type is :component and the title has no square brackets in it" do ref = Puppet::Resource.new(:component, "foo") ref.type.should == "Class" ref.title.should == "Foo" end it "should interpret the title as a reference and assign appropriately if the type is :component and the title contains square brackets" do ref = Puppet::Resource.new(:component, "foo::bar[yay]") ref.type.should == "Foo::Bar" ref.title.should == "yay" end it "should set the type to 'Class' if it is nil and the title contains no square brackets" do ref = Puppet::Resource.new(nil, "yay") ref.type.should == "Class" ref.title.should == "Yay" end it "should interpret the title as a reference and assign appropriately if the type is nil and the title contains square brackets" do ref = Puppet::Resource.new(nil, "foo::bar[yay]") ref.type.should == "Foo::Bar" ref.title.should == "yay" end it "should interpret the title as a reference and assign appropriately if the type is nil and the title contains nested square brackets" do ref = Puppet::Resource.new(nil, "foo::bar[baz[yay]]") ref.type.should == "Foo::Bar" ref.title.should =="baz[yay]" end it "should interpret the type as a reference and assign appropriately if the title is nil and the type contains square brackets" do ref = Puppet::Resource.new("foo::bar[baz]") ref.type.should == "Foo::Bar" ref.title.should =="baz" end it "should be able to extract its information from a Puppet::Type instance" do ral = Puppet::Type.type(:file).new :path => basepath+"/foo" ref = Puppet::Resource.new(ral) ref.type.should == "File" ref.title.should == basepath+"/foo" end it "should fail if the title is nil and the type is not a valid resource reference string" do - expect { Puppet::Resource.new("resource-spec-foo") }.should raise_error(ArgumentError) + expect { Puppet::Resource.new("resource-spec-foo") }.to raise_error(ArgumentError) end it 'should fail if strict is set and type does not exist' do - expect { Puppet::Resource.new('resource-spec-foo', 'title', {:strict=>true}) }.should raise_error(ArgumentError, 'Invalid resource type resource-spec-foo') + expect { Puppet::Resource.new('resource-spec-foo', 'title', {:strict=>true}) }.to raise_error(ArgumentError, 'Invalid resource type resource-spec-foo') end it 'should fail if strict is set and class does not exist' do - expect { Puppet::Resource.new('Class', 'resource-spec-foo', {:strict=>true}) }.should raise_error(ArgumentError, 'Could not find declared class resource-spec-foo') + expect { Puppet::Resource.new('Class', 'resource-spec-foo', {:strict=>true}) }.to raise_error(ArgumentError, 'Could not find declared class resource-spec-foo') end it "should fail if the title is a hash and the type is not a valid resource reference string" do expect { Puppet::Resource.new({:type => "resource-spec-foo", :title => "bar"}) }. to raise_error ArgumentError, /Puppet::Resource.new does not take a hash/ end it "should be able to produce a backward-compatible reference array" do Puppet::Resource.new("foobar", "/f").to_trans_ref.should == %w{Foobar /f} end it "should be taggable" do Puppet::Resource.ancestors.should be_include(Puppet::Util::Tagging) end it "should have an 'exported' attribute" do resource = Puppet::Resource.new("file", "/f") resource.exported = true resource.exported.should == true resource.should be_exported end it "should support an environment attribute" do Puppet::Resource.new("file", "/my/file", :environment => :foo).environment.name.should == :foo end describe "and munging its type and title" do describe "when modeling a builtin resource" do it "should be able to find the resource type" do Puppet::Resource.new("file", "/my/file").resource_type.should equal(Puppet::Type.type(:file)) end it "should set its type to the capitalized type name" do Puppet::Resource.new("file", "/my/file").type.should == "File" end end describe "when modeling a defined resource" do describe "that exists" do before do @type = Puppet::Resource::Type.new(:definition, "foo::bar") Puppet::Node::Environment.new.known_resource_types.add @type end it "should set its type to the capitalized type name" do Puppet::Resource.new("foo::bar", "/my/file").type.should == "Foo::Bar" end it "should be able to find the resource type" do Puppet::Resource.new("foo::bar", "/my/file").resource_type.should equal(@type) end it "should set its title to the provided title" do Puppet::Resource.new("foo::bar", "/my/file").title.should == "/my/file" end end describe "that does not exist" do it "should set its resource type to the capitalized resource type name" do Puppet::Resource.new("foo::bar", "/my/file").type.should == "Foo::Bar" end end end describe "when modeling a node" do # Life's easier with nodes, because they can't be qualified. it "should set its type to 'Node' and its title to the provided title" do node = Puppet::Resource.new("node", "foo") node.type.should == "Node" node.title.should == "foo" end end describe "when modeling a class" do it "should set its type to 'Class'" do Puppet::Resource.new("class", "foo").type.should == "Class" end describe "that exists" do before do @type = Puppet::Resource::Type.new(:hostclass, "foo::bar") Puppet::Node::Environment.new.known_resource_types.add @type end it "should set its title to the capitalized, fully qualified resource type" do Puppet::Resource.new("class", "foo::bar").title.should == "Foo::Bar" end it "should be able to find the resource type" do Puppet::Resource.new("class", "foo::bar").resource_type.should equal(@type) end end describe "that does not exist" do it "should set its type to 'Class' and its title to the capitalized provided name" do klass = Puppet::Resource.new("class", "foo::bar") klass.type.should == "Class" klass.title.should == "Foo::Bar" end end describe "and its name is set to the empty string" do it "should set its title to :main" do Puppet::Resource.new("class", "").title.should == :main end describe "and a class exists whose name is the empty string" do # this was a bit tough to track down it "should set its title to :main" do @type = Puppet::Resource::Type.new(:hostclass, "") Puppet::Node::Environment.new.known_resource_types.add @type Puppet::Resource.new("class", "").title.should == :main end end end describe "and its name is set to :main" do it "should set its title to :main" do Puppet::Resource.new("class", :main).title.should == :main end describe "and a class exists whose name is the empty string" do # this was a bit tough to track down it "should set its title to :main" do @type = Puppet::Resource::Type.new(:hostclass, "") Puppet::Node::Environment.new.known_resource_types.add @type Puppet::Resource.new("class", :main).title.should == :main end end end end end it "should return nil when looking up resource types that don't exist" do Puppet::Resource.new("foobar", "bar").resource_type.should be_nil end it "should not fail when an invalid parameter is used and strict mode is disabled" do type = Puppet::Resource::Type.new(:definition, "foobar") Puppet::Node::Environment.new.known_resource_types.add type resource = Puppet::Resource.new("foobar", "/my/file") resource[:yay] = true end it "should be considered equivalent to another resource if their type and title match and no parameters are set" do Puppet::Resource.new("file", "/f").should == Puppet::Resource.new("file", "/f") end it "should be considered equivalent to another resource if their type, title, and parameters are equal" do Puppet::Resource.new("file", "/f", :parameters => {:foo => "bar"}).should == Puppet::Resource.new("file", "/f", :parameters => {:foo => "bar"}) end it "should not be considered equivalent to another resource if their type and title match but parameters are different" do Puppet::Resource.new("file", "/f", :parameters => {:fee => "baz"}).should_not == Puppet::Resource.new("file", "/f", :parameters => {:foo => "bar"}) end it "should not be considered equivalent to a non-resource" do Puppet::Resource.new("file", "/f").should_not == "foo" end it "should not be considered equivalent to another resource if their types do not match" do Puppet::Resource.new("file", "/f").should_not == Puppet::Resource.new("exec", "/f") end it "should not be considered equivalent to another resource if their titles do not match" do Puppet::Resource.new("file", "/foo").should_not == Puppet::Resource.new("file", "/f") end describe "when referring to a resource with name canonicalization" do it "should canonicalize its own name" do res = Puppet::Resource.new("file", "/path/") res.uniqueness_key.should == ["/path"] res.ref.should == "File[/path/]" end end describe "when running in strict mode" do it "should be strict" do Puppet::Resource.new("file", "/path", :strict => true).should be_strict end it "should fail if invalid parameters are used" do - expect { Puppet::Resource.new("file", "/path", :strict => true, :parameters => {:nosuchparam => "bar"}) }.should raise_error + expect { Puppet::Resource.new("file", "/path", :strict => true, :parameters => {:nosuchparam => "bar"}) }.to raise_error end it "should fail if the resource type cannot be resolved" do - expect { Puppet::Resource.new("nosuchtype", "/path", :strict => true) }.should raise_error + expect { Puppet::Resource.new("nosuchtype", "/path", :strict => true) }.to raise_error end end describe "when managing parameters" do before do @resource = Puppet::Resource.new("file", "/my/file") end it "should correctly detect when provided parameters are not valid for builtin types" do Puppet::Resource.new("file", "/my/file").should_not be_valid_parameter("foobar") end it "should correctly detect when provided parameters are valid for builtin types" do Puppet::Resource.new("file", "/my/file").should be_valid_parameter("mode") end it "should correctly detect when provided parameters are not valid for defined resource types" do type = Puppet::Resource::Type.new(:definition, "foobar") Puppet::Node::Environment.new.known_resource_types.add type Puppet::Resource.new("foobar", "/my/file").should_not be_valid_parameter("myparam") end it "should correctly detect when provided parameters are valid for defined resource types" do type = Puppet::Resource::Type.new(:definition, "foobar", :arguments => {"myparam" => nil}) Puppet::Node::Environment.new.known_resource_types.add type Puppet::Resource.new("foobar", "/my/file").should be_valid_parameter("myparam") end it "should allow setting and retrieving of parameters" do @resource[:foo] = "bar" @resource[:foo].should == "bar" end it "should allow setting of parameters at initialization" do Puppet::Resource.new("file", "/my/file", :parameters => {:foo => "bar"})[:foo].should == "bar" end it "should canonicalize retrieved parameter names to treat symbols and strings equivalently" do @resource[:foo] = "bar" @resource["foo"].should == "bar" end it "should canonicalize set parameter names to treat symbols and strings equivalently" do @resource["foo"] = "bar" @resource[:foo].should == "bar" end it "should set the namevar when asked to set the name" do resource = Puppet::Resource.new("user", "bob") Puppet::Type.type(:user).stubs(:key_attributes).returns [:myvar] resource[:name] = "bob" resource[:myvar].should == "bob" end it "should return the namevar when asked to return the name" do resource = Puppet::Resource.new("user", "bob") Puppet::Type.type(:user).stubs(:key_attributes).returns [:myvar] resource[:myvar] = "test" resource[:name].should == "test" end it "should be able to set the name for non-builtin types" do resource = Puppet::Resource.new(:foo, "bar") resource[:name] = "eh" - expect { resource[:name] = "eh" }.should_not raise_error + expect { resource[:name] = "eh" }.to_not raise_error end it "should be able to return the name for non-builtin types" do resource = Puppet::Resource.new(:foo, "bar") resource[:name] = "eh" resource[:name].should == "eh" end it "should be able to iterate over parameters" do @resource[:foo] = "bar" @resource[:fee] = "bare" params = {} @resource.each do |key, value| params[key] = value end params.should == {:foo => "bar", :fee => "bare"} end it "should include Enumerable" do @resource.class.ancestors.should be_include(Enumerable) end it "should have a method for testing whether a parameter is included" do @resource[:foo] = "bar" @resource.should be_has_key(:foo) @resource.should_not be_has_key(:eh) end it "should have a method for providing the list of parameters" do @resource[:foo] = "bar" @resource[:bar] = "foo" keys = @resource.keys keys.should be_include(:foo) keys.should be_include(:bar) end it "should have a method for providing the number of parameters" do @resource[:foo] = "bar" @resource.length.should == 1 end it "should have a method for deleting parameters" do @resource[:foo] = "bar" @resource.delete(:foo) @resource[:foo].should be_nil end it "should have a method for testing whether the parameter list is empty" do @resource.should be_empty @resource[:foo] = "bar" @resource.should_not be_empty end it "should be able to produce a hash of all existing parameters" do @resource[:foo] = "bar" @resource[:fee] = "yay" hash = @resource.to_hash hash[:foo].should == "bar" hash[:fee].should == "yay" end it "should not provide direct access to the internal parameters hash when producing a hash" do hash = @resource.to_hash hash[:foo] = "bar" @resource[:foo].should be_nil end it "should use the title as the namevar to the hash if no namevar is present" do resource = Puppet::Resource.new("user", "bob") Puppet::Type.type(:user).stubs(:key_attributes).returns [:myvar] resource.to_hash[:myvar].should == "bob" end it "should set :name to the title if :name is not present for non-builtin types" do krt = Puppet::Resource::TypeCollection.new("myenv") krt.add Puppet::Resource::Type.new(:definition, :foo) resource = Puppet::Resource.new :foo, "bar" resource.stubs(:known_resource_types).returns krt resource.to_hash[:name].should == "bar" end end describe "when serializing" do before do @resource = Puppet::Resource.new("file", "/my/file") @resource["one"] = "test" @resource["two"] = "other" end it "should be able to be dumped to yaml" do - proc { YAML.dump(@resource) }.should_not raise_error + expect { YAML.dump(@resource) }.to_not raise_error end it "should produce an equivalent yaml object" do text = YAML.dump(@resource) newresource = YAML.load(text) newresource.title.should == @resource.title newresource.type.should == @resource.type %w{one two}.each do |param| newresource[param].should == @resource[param] end end end describe "when loading 0.25.x storedconfigs YAML" do before :each do @old_storedconfig_yaml = %q{--- !ruby/object:Puppet::Resource::Reference builtin_type: title: /tmp/bar type: File } end it "should deserialize a Puppet::Resource::Reference without exceptions" do - expect { YAML.load(@old_storedconfig_yaml) }.should_not raise_error + expect { YAML.load(@old_storedconfig_yaml) }.to_not raise_error end it "should deserialize as a Puppet::Resource::Reference as a Puppet::Resource" do YAML.load(@old_storedconfig_yaml).class.should == Puppet::Resource end it "should to_hash properly" do YAML.load(@old_storedconfig_yaml).to_hash.should == { :path => "/tmp/bar" } end end describe "when converting to a RAL resource" do it "should use the resource type's :new method to create the resource if the resource is of a builtin type" do resource = Puppet::Resource.new("file", basepath+"/my/file") result = resource.to_ral result.must be_instance_of(Puppet::Type.type(:file)) result[:path].should == basepath+"/my/file" end it "should convert to a component instance if the resource type is not of a builtin type" do resource = Puppet::Resource.new("foobar", "somename") result = resource.to_ral result.must be_instance_of(Puppet::Type.type(:component)) result.title.should == "Foobar[somename]" end end describe "when converting to puppet code" do before do @resource = Puppet::Resource.new("one::two", "/my/file", :parameters => { :noop => true, :foo => %w{one two}, :ensure => 'present', } ) end it "should align, sort and add trailing commas to attributes with ensure first", :'fails_on_ruby_1.9.2' => true do @resource.to_manifest.should == <<-HEREDOC.gsub(/^\s{8}/, '').gsub(/\n$/, '') one::two { '/my/file': ensure => 'present', foo => ['one', 'two'], noop => 'true', } HEREDOC end end describe "when converting to a TransObject" do describe "and the resource is not an instance of a builtin type" do before do @resource = Puppet::Resource.new("resource-spec-foo", "bar") end it "should return a simple TransBucket if it is not an instance of a builtin type" do bucket = @resource.to_trans bucket.should be_instance_of(Puppet::TransBucket) bucket.type.should == @resource.type bucket.name.should == @resource.title end it "should return a simple TransBucket if it is a stage" do @resource = Puppet::Resource.new("stage", "bar") bucket = @resource.to_trans bucket.should be_instance_of(Puppet::TransBucket) bucket.type.should == @resource.type bucket.name.should == @resource.title end it "should copy over the resource's file" do @resource.file = "/foo/bar" @resource.to_trans.file.should == "/foo/bar" end it "should copy over the resource's line" do @resource.line = 50 @resource.to_trans.line.should == 50 end end describe "and the resource is an instance of a builtin type" do before do @resource = Puppet::Resource.new("file", "bar") end it "should return a TransObject if it is an instance of a builtin resource type" do trans = @resource.to_trans trans.should be_instance_of(Puppet::TransObject) trans.type.should == "file" trans.name.should == @resource.title end it "should copy over the resource's file" do @resource.file = "/foo/bar" @resource.to_trans.file.should == "/foo/bar" end it "should copy over the resource's line" do @resource.line = 50 @resource.to_trans.line.should == 50 end # Only TransObjects support tags, annoyingly it "should copy over the resource's tags" do @resource.tag "foo" @resource.to_trans.tags.should == @resource.tags end it "should copy the resource's parameters into the transobject and convert the parameter name to a string" do @resource[:foo] = "bar" @resource.to_trans["foo"].should == "bar" end it "should be able to copy arrays of values" do @resource[:foo] = %w{yay fee} @resource.to_trans["foo"].should == %w{yay fee} end it "should reduce single-value arrays to just a value" do @resource[:foo] = %w{yay} @resource.to_trans["foo"].should == "yay" end it "should convert resource references into the backward-compatible form" do @resource[:foo] = Puppet::Resource.new(:file, "/f") @resource.to_trans["foo"].should == %w{File /f} end it "should convert resource references into the backward-compatible form even when within arrays" do @resource[:foo] = ["a", Puppet::Resource.new(:file, "/f")] @resource.to_trans["foo"].should == ["a", %w{File /f}] end end end describe "when converting to pson", :if => Puppet.features.pson? do def pson_output_should @resource.class.expects(:pson_create).with { |hash| yield hash } end it "should include the pson util module" do Puppet::Resource.singleton_class.ancestors.should be_include(Puppet::Util::Pson) end # LAK:NOTE For all of these tests, we convert back to the resource so we can # trap the actual data structure then. it "should set its type to the provided type" do Puppet::Resource.from_pson(PSON.parse(Puppet::Resource.new("File", "/foo").to_pson)).type.should == "File" end it "should set its title to the provided title" do Puppet::Resource.from_pson(PSON.parse(Puppet::Resource.new("File", "/foo").to_pson)).title.should == "/foo" end it "should include all tags from the resource" do resource = Puppet::Resource.new("File", "/foo") resource.tag("yay") Puppet::Resource.from_pson(PSON.parse(resource.to_pson)).tags.should == resource.tags end it "should include the file if one is set" do resource = Puppet::Resource.new("File", "/foo") resource.file = "/my/file" Puppet::Resource.from_pson(PSON.parse(resource.to_pson)).file.should == "/my/file" end it "should include the line if one is set" do resource = Puppet::Resource.new("File", "/foo") resource.line = 50 Puppet::Resource.from_pson(PSON.parse(resource.to_pson)).line.should == 50 end it "should include the 'exported' value if one is set" do resource = Puppet::Resource.new("File", "/foo") resource.exported = true Puppet::Resource.from_pson(PSON.parse(resource.to_pson)).exported.should be_true end it "should set 'exported' to false if no value is set" do resource = Puppet::Resource.new("File", "/foo") Puppet::Resource.from_pson(PSON.parse(resource.to_pson)).exported.should be_false end it "should set all of its parameters as the 'parameters' entry" do resource = Puppet::Resource.new("File", "/foo") resource[:foo] = %w{bar eh} resource[:fee] = %w{baz} result = Puppet::Resource.from_pson(PSON.parse(resource.to_pson)) result["foo"].should == %w{bar eh} result["fee"].should == %w{baz} end it "should serialize relationships as reference strings" do resource = Puppet::Resource.new("File", "/foo") resource[:requires] = Puppet::Resource.new("File", "/bar") result = Puppet::Resource.from_pson(PSON.parse(resource.to_pson)) result[:requires].should == "File[/bar]" end it "should serialize multiple relationships as arrays of reference strings" do resource = Puppet::Resource.new("File", "/foo") resource[:requires] = [Puppet::Resource.new("File", "/bar"), Puppet::Resource.new("File", "/baz")] result = Puppet::Resource.from_pson(PSON.parse(resource.to_pson)) result[:requires].should == [ "File[/bar]", "File[/baz]" ] end end describe "when converting from pson", :if => Puppet.features.pson? do def pson_result_should Puppet::Resource.expects(:new).with { |hash| yield hash } end before do @data = { 'type' => "file", 'title' => basepath+"/yay", } end it "should set its type to the provided type" do Puppet::Resource.from_pson(@data).type.should == "File" end it "should set its title to the provided title" do Puppet::Resource.from_pson(@data).title.should == basepath+"/yay" end it "should tag the resource with any provided tags" do @data['tags'] = %w{foo bar} resource = Puppet::Resource.from_pson(@data) resource.tags.should be_include("foo") resource.tags.should be_include("bar") end it "should set its file to the provided file" do @data['file'] = "/foo/bar" Puppet::Resource.from_pson(@data).file.should == "/foo/bar" end it "should set its line to the provided line" do @data['line'] = 50 Puppet::Resource.from_pson(@data).line.should == 50 end it "should 'exported' to true if set in the pson data" do @data['exported'] = true Puppet::Resource.from_pson(@data).exported.should be_true end it "should 'exported' to false if not set in the pson data" do Puppet::Resource.from_pson(@data).exported.should be_false end it "should fail if no title is provided" do @data.delete('title') - expect { Puppet::Resource.from_pson(@data) }.should raise_error(ArgumentError) + expect { Puppet::Resource.from_pson(@data) }.to raise_error(ArgumentError) end it "should fail if no type is provided" do @data.delete('type') - expect { Puppet::Resource.from_pson(@data) }.should raise_error(ArgumentError) + expect { Puppet::Resource.from_pson(@data) }.to raise_error(ArgumentError) end it "should set each of the provided parameters" do @data['parameters'] = {'foo' => %w{one two}, 'fee' => %w{three four}} resource = Puppet::Resource.from_pson(@data) resource['foo'].should == %w{one two} resource['fee'].should == %w{three four} end it "should convert single-value array parameters to normal values" do @data['parameters'] = {'foo' => %w{one}} resource = Puppet::Resource.from_pson(@data) resource['foo'].should == %w{one} end end describe "it should implement to_resource" do resource = Puppet::Resource.new("file", "/my/file") resource.to_resource.should == resource end describe "because it is an indirector model" do it "should include Puppet::Indirector" do Puppet::Resource.should be_is_a(Puppet::Indirector) end it "should have a default terminus" do Puppet::Resource.indirection.terminus_class.should be end it "should have a name" do Puppet::Resource.new("file", "/my/file").name.should == "File//my/file" end end describe "when resolving resources with a catalog" do it "should resolve all resources using the catalog" do catalog = mock 'catalog' resource = Puppet::Resource.new("foo::bar", "yay") resource.catalog = catalog catalog.expects(:resource).with("Foo::Bar[yay]").returns(:myresource) resource.resolve.should == :myresource end end describe "when generating the uniqueness key" do it "should include all of the key_attributes in alphabetical order by attribute name" do Puppet::Type.type(:file).stubs(:key_attributes).returns [:myvar, :owner, :path] Puppet::Type.type(:file).stubs(:title_patterns).returns( [ [ /(.*)/, [ [:path, lambda{|x| x} ] ] ] ] ) res = Puppet::Resource.new("file", "/my/file", :parameters => {:owner => 'root', :content => 'hello'}) res.uniqueness_key.should == [ nil, 'root', '/my/file'] end end describe "#prune_parameters" do before do Puppet.newtype('blond') do newproperty(:ensure) newproperty(:height) newproperty(:weight) newproperty(:sign) newproperty(:friends) newparam(:admits_to_dying_hair) newparam(:admits_to_age) newparam(:name) end end it "should strip all parameters and strip properties that are nil, empty or absent except for ensure" do resource = Puppet::Resource.new("blond", "Bambi", :parameters => { :ensure => 'absent', :height => '', :weight => 'absent', :friends => [], :admits_to_age => true, :admits_to_dying_hair => false }) pruned_resource = resource.prune_parameters pruned_resource.should == Puppet::Resource.new("blond", "Bambi", :parameters => {:ensure => 'absent'}) end it "should leave parameters alone if in parameters_to_include" do resource = Puppet::Resource.new("blond", "Bambi", :parameters => { :admits_to_age => true, :admits_to_dying_hair => false }) pruned_resource = resource.prune_parameters(:parameters_to_include => [:admits_to_dying_hair]) pruned_resource.should == Puppet::Resource.new("blond", "Bambi", :parameters => {:admits_to_dying_hair => false}) end it "should leave properties if not nil, absent or empty" do resource = Puppet::Resource.new("blond", "Bambi", :parameters => { :ensure => 'silly', :height => '7 ft 5 in', :friends => ['Oprah'], }) pruned_resource = resource.prune_parameters pruned_resource.should == resource = Puppet::Resource.new("blond", "Bambi", :parameters => { :ensure => 'silly', :height => '7 ft 5 in', :friends => ['Oprah'], }) end end end diff --git a/spec/unit/simple_graph_spec.rb b/spec/unit/simple_graph_spec.rb index 2cd00bda9..50f43c4aa 100755 --- a/spec/unit/simple_graph_spec.rb +++ b/spec/unit/simple_graph_spec.rb @@ -1,919 +1,919 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/simple_graph' describe Puppet::SimpleGraph do it "should return the number of its vertices as its length" do @graph = Puppet::SimpleGraph.new @graph.add_vertex("one") @graph.add_vertex("two") @graph.size.should == 2 end it "should consider itself a directed graph" do Puppet::SimpleGraph.new.directed?.should be_true end it "should provide a method for reversing the graph" do @graph = Puppet::SimpleGraph.new @graph.add_edge(:one, :two) @graph.reversal.edge?(:two, :one).should be_true end it "should be able to produce a dot graph" do @graph = Puppet::SimpleGraph.new @graph.add_edge(:one, :two) - proc { @graph.to_dot_graph }.should_not raise_error + expect { @graph.to_dot_graph }.to_not raise_error end describe "when managing vertices" do before do @graph = Puppet::SimpleGraph.new end it "should provide a method to add a vertex" do @graph.add_vertex(:test) @graph.vertex?(:test).should be_true end it "should reset its reversed graph when vertices are added" do rev = @graph.reversal @graph.add_vertex(:test) @graph.reversal.should_not equal(rev) end it "should ignore already-present vertices when asked to add a vertex" do @graph.add_vertex(:test) - proc { @graph.add_vertex(:test) }.should_not raise_error + expect { @graph.add_vertex(:test) }.to_not raise_error end it "should return true when asked if a vertex is present" do @graph.add_vertex(:test) @graph.vertex?(:test).should be_true end it "should return false when asked if a non-vertex is present" do @graph.vertex?(:test).should be_false end it "should return all set vertices when asked" do @graph.add_vertex(:one) @graph.add_vertex(:two) @graph.vertices.length.should == 2 @graph.vertices.should include(:one) @graph.vertices.should include(:two) end it "should remove a given vertex when asked" do @graph.add_vertex(:one) @graph.remove_vertex!(:one) @graph.vertex?(:one).should be_false end it "should do nothing when a non-vertex is asked to be removed" do - proc { @graph.remove_vertex!(:one) }.should_not raise_error + expect { @graph.remove_vertex!(:one) }.to_not raise_error end end describe "when managing edges" do before do @graph = Puppet::SimpleGraph.new end it "should provide a method to test whether a given vertex pair is an edge" do @graph.should respond_to(:edge?) end it "should reset its reversed graph when edges are added" do rev = @graph.reversal @graph.add_edge(:one, :two) @graph.reversal.should_not equal(rev) end it "should provide a method to add an edge as an instance of the edge class" do edge = Puppet::Relationship.new(:one, :two) @graph.add_edge(edge) @graph.edge?(:one, :two).should be_true end it "should provide a method to add an edge by specifying the two vertices" do @graph.add_edge(:one, :two) @graph.edge?(:one, :two).should be_true end it "should provide a method to add an edge by specifying the two vertices and a label" do @graph.add_edge(:one, :two, :callback => :awesome) @graph.edge?(:one, :two).should be_true end describe "when retrieving edges between two nodes" do it "should handle the case of nodes not in the graph" do @graph.edges_between(:one, :two).should == [] end it "should handle the case of nodes with no edges between them" do @graph.add_vertex(:one) @graph.add_vertex(:two) @graph.edges_between(:one, :two).should == [] end it "should handle the case of nodes connected by a single edge" do edge = Puppet::Relationship.new(:one, :two) @graph.add_edge(edge) @graph.edges_between(:one, :two).length.should == 1 @graph.edges_between(:one, :two)[0].should equal(edge) end it "should handle the case of nodes connected by multiple edges" do edge1 = Puppet::Relationship.new(:one, :two, :callback => :foo) edge2 = Puppet::Relationship.new(:one, :two, :callback => :bar) @graph.add_edge(edge1) @graph.add_edge(edge2) Set.new(@graph.edges_between(:one, :two)).should == Set.new([edge1, edge2]) end end it "should add the edge source as a vertex if it is not already" do edge = Puppet::Relationship.new(:one, :two) @graph.add_edge(edge) @graph.vertex?(:one).should be_true end it "should add the edge target as a vertex if it is not already" do edge = Puppet::Relationship.new(:one, :two) @graph.add_edge(edge) @graph.vertex?(:two).should be_true end it "should return all edges as edge instances when asked" do one = Puppet::Relationship.new(:one, :two) two = Puppet::Relationship.new(:two, :three) @graph.add_edge(one) @graph.add_edge(two) edges = @graph.edges edges.should be_instance_of(Array) edges.length.should == 2 edges.should include(one) edges.should include(two) end it "should remove an edge when asked" do edge = Puppet::Relationship.new(:one, :two) @graph.add_edge(edge) @graph.remove_edge!(edge) @graph.edge?(edge.source, edge.target).should be_false end it "should remove all related edges when a vertex is removed" do one = Puppet::Relationship.new(:one, :two) two = Puppet::Relationship.new(:two, :three) @graph.add_edge(one) @graph.add_edge(two) @graph.remove_vertex!(:two) @graph.edge?(:one, :two).should be_false @graph.edge?(:two, :three).should be_false @graph.edges.length.should == 0 end end describe "when finding adjacent vertices" do before do @graph = Puppet::SimpleGraph.new @one_two = Puppet::Relationship.new(:one, :two) @two_three = Puppet::Relationship.new(:two, :three) @one_three = Puppet::Relationship.new(:one, :three) @graph.add_edge(@one_two) @graph.add_edge(@one_three) @graph.add_edge(@two_three) end it "should return adjacent vertices" do adj = @graph.adjacent(:one) adj.should be_include(:three) adj.should be_include(:two) end it "should default to finding :out vertices" do @graph.adjacent(:two).should == [:three] end it "should support selecting :in vertices" do @graph.adjacent(:two, :direction => :in).should == [:one] end it "should default to returning the matching vertices as an array of vertices" do @graph.adjacent(:two).should == [:three] end it "should support returning an array of matching edges" do @graph.adjacent(:two, :type => :edges).should == [@two_three] end # Bug #2111 it "should not consider a vertex adjacent just because it was asked about previously" do @graph = Puppet::SimpleGraph.new @graph.add_vertex("a") @graph.add_vertex("b") @graph.edge?("a", "b") @graph.adjacent("a").should == [] end end describe "when clearing" do before do @graph = Puppet::SimpleGraph.new one = Puppet::Relationship.new(:one, :two) two = Puppet::Relationship.new(:two, :three) @graph.add_edge(one) @graph.add_edge(two) @graph.clear end it "should remove all vertices" do @graph.vertices.should be_empty end it "should remove all edges" do @graph.edges.should be_empty end end describe "when reversing graphs" do before do @graph = Puppet::SimpleGraph.new end it "should provide a method for reversing the graph" do @graph.add_edge(:one, :two) @graph.reversal.edge?(:two, :one).should be_true end it "should add all vertices to the reversed graph" do @graph.add_edge(:one, :two) @graph.vertex?(:one).should be_true @graph.vertex?(:two).should be_true end it "should retain labels on edges" do @graph.add_edge(:one, :two, :callback => :awesome) edge = @graph.reversal.edges_between(:two, :one)[0] edge.label.should == {:callback => :awesome} end end describe "when reporting cycles in the graph" do before do @graph = Puppet::SimpleGraph.new end # This works with `add_edges` to auto-vivify the resource instances. let :vertex do Hash.new do |hash, key| hash[key] = Puppet::Type.type(:notify).new(:name => key.to_s) end end def add_edges(hash) hash.each do |a,b| @graph.add_edge(vertex[a], vertex[b]) end end def simplify(cycles) cycles.map do |x| x.map do |y| y.to_s.match(/^Notify\[(.*)\]$/)[1] end end end it "should fail on two-vertex loops" do add_edges :a => :b, :b => :a - proc { @graph.report_cycles_in_graph }.should raise_error(Puppet::Error) + expect { @graph.report_cycles_in_graph }.to raise_error(Puppet::Error) end it "should fail on multi-vertex loops" do add_edges :a => :b, :b => :c, :c => :a - proc { @graph.report_cycles_in_graph }.should raise_error(Puppet::Error) + expect { @graph.report_cycles_in_graph }.to raise_error(Puppet::Error) end it "should fail when a larger tree contains a small cycle" do add_edges :a => :b, :b => :a, :c => :a, :d => :c - proc { @graph.report_cycles_in_graph }.should raise_error(Puppet::Error) + expect { @graph.report_cycles_in_graph }.to raise_error(Puppet::Error) end it "should succeed on trees with no cycles" do add_edges :a => :b, :b => :e, :c => :a, :d => :c - proc { @graph.report_cycles_in_graph }.should_not raise_error + expect { @graph.report_cycles_in_graph }.to_not raise_error end it "should produce the correct relationship text" do add_edges :a => :b, :b => :a # cycle detection starts from a or b randomly # so we need to check for either ordering in the error message want = %r{Found 1 dependency cycle:\n\((Notify\[a\] => Notify\[b\] => Notify\[a\]|Notify\[b\] => Notify\[a\] => Notify\[b\])\)\nTry} expect { @graph.report_cycles_in_graph }.to raise_error(Puppet::Error, want) end it "cycle discovery should be the minimum cycle for a simple graph" do add_edges "a" => "b" add_edges "b" => "a" add_edges "b" => "c" cycles = nil - expect { cycles = @graph.find_cycles_in_graph }.should_not raise_error + expect { cycles = @graph.find_cycles_in_graph }.to_not raise_error simplify(cycles).should be == [["a", "b"]] end it "cycle discovery should handle two distinct cycles" do add_edges "a" => "a1", "a1" => "a" add_edges "b" => "b1", "b1" => "b" cycles = nil - expect { cycles = @graph.find_cycles_in_graph }.should_not raise_error + expect { cycles = @graph.find_cycles_in_graph }.to_not raise_error simplify(cycles).should be == [["a1", "a"], ["b1", "b"]] end it "cycle discovery should handle two cycles in a connected graph" do add_edges "a" => "b", "b" => "c", "c" => "d" add_edges "a" => "a1", "a1" => "a" add_edges "c" => "c1", "c1" => "c2", "c2" => "c3", "c3" => "c" cycles = nil - expect { cycles = @graph.find_cycles_in_graph }.should_not raise_error + expect { cycles = @graph.find_cycles_in_graph }.to_not raise_error simplify(cycles).should be == [%w{a1 a}, %w{c1 c2 c3 c}] end it "cycle discovery should handle a complicated cycle" do add_edges "a" => "b", "b" => "c" add_edges "a" => "c" add_edges "c" => "c1", "c1" => "a" add_edges "c" => "c2", "c2" => "b" cycles = nil - expect { cycles = @graph.find_cycles_in_graph }.should_not raise_error + expect { cycles = @graph.find_cycles_in_graph }.to_not raise_error simplify(cycles).should be == [%w{a b c1 c2 c}] end it "cycle discovery should not fail with large data sets" do limit = 3000 (1..(limit - 1)).each do |n| add_edges n.to_s => (n+1).to_s end cycles = nil - expect { cycles = @graph.find_cycles_in_graph }.should_not raise_error + expect { cycles = @graph.find_cycles_in_graph }.to_not raise_error simplify(cycles).should be == [] end it "path finding should work with a simple cycle" do add_edges "a" => "b", "b" => "c", "c" => "a" cycles = @graph.find_cycles_in_graph paths = @graph.paths_in_cycle(cycles.first, 100) simplify(paths).should be == [%w{a b c a}] end it "path finding should work with two independent cycles" do add_edges "a" => "b1" add_edges "a" => "b2" add_edges "b1" => "a", "b2" => "a" cycles = @graph.find_cycles_in_graph cycles.length.should be == 1 paths = @graph.paths_in_cycle(cycles.first, 100) simplify(paths).should be == [%w{a b1 a}, %w{a b2 a}] end it "path finding should prefer shorter paths in cycles" do add_edges "a" => "b", "b" => "c", "c" => "a" add_edges "b" => "a" cycles = @graph.find_cycles_in_graph cycles.length.should be == 1 paths = @graph.paths_in_cycle(cycles.first, 100) simplify(paths).should be == [%w{a b a}, %w{a b c a}] end it "path finding should respect the max_path value" do (1..20).each do |n| add_edges "a" => "b#{n}", "b#{n}" => "a" end cycles = @graph.find_cycles_in_graph cycles.length.should be == 1 (1..20).each do |n| paths = @graph.paths_in_cycle(cycles.first, n) paths.length.should be == n end paths = @graph.paths_in_cycle(cycles.first, 21) paths.length.should be == 20 end end describe "when writing dot files" do before do @graph = Puppet::SimpleGraph.new @name = :test @file = File.join(Puppet[:graphdir], @name.to_s + ".dot") end it "should only write when graphing is enabled" do File.expects(:open).with(@file).never Puppet[:graph] = false @graph.write_graph(@name) end it "should write a dot file based on the passed name" do File.expects(:open).with(@file, "w").yields(stub("file", :puts => nil)) @graph.expects(:to_dot).with("name" => @name.to_s.capitalize) Puppet[:graph] = true @graph.write_graph(@name) end after do Puppet.settings.clear end end describe Puppet::SimpleGraph do before do @graph = Puppet::SimpleGraph.new end it "should correctly clear vertices and edges when asked" do @graph.add_edge("a", "b") @graph.add_vertex "c" @graph.clear @graph.vertices.should be_empty @graph.edges.should be_empty end end describe "when matching edges", :'fails_on_ruby_1.9.2' => true do before do @graph = Puppet::SimpleGraph.new @event = Puppet::Transaction::Event.new(:name => :yay, :resource => "a") @none = Puppet::Transaction::Event.new(:name => :NONE, :resource => "a") @edges = {} @edges["a/b"] = Puppet::Relationship.new("a", "b", {:event => :yay, :callback => :refresh}) @edges["a/c"] = Puppet::Relationship.new("a", "c", {:event => :yay, :callback => :refresh}) @graph.add_edge(@edges["a/b"]) end it "should match edges whose source matches the source of the event" do @graph.matching_edges(@event).should == [@edges["a/b"]] end it "should match always match nothing when the event is :NONE" do @graph.matching_edges(@none).should be_empty end it "should match multiple edges" do @graph.add_edge(@edges["a/c"]) edges = @graph.matching_edges(@event) edges.should be_include(@edges["a/b"]) edges.should be_include(@edges["a/c"]) end end describe "when determining dependencies" do before do @graph = Puppet::SimpleGraph.new @graph.add_edge("a", "b") @graph.add_edge("a", "c") @graph.add_edge("b", "d") end it "should find all dependents when they are on multiple levels" do @graph.dependents("a").sort.should == %w{b c d}.sort end it "should find single dependents" do @graph.dependents("b").sort.should == %w{d}.sort end it "should return an empty array when there are no dependents" do @graph.dependents("c").sort.should == [].sort end it "should find all dependencies when they are on multiple levels" do @graph.dependencies("d").sort.should == %w{a b} end it "should find single dependencies" do @graph.dependencies("c").sort.should == %w{a} end it "should return an empty array when there are no dependencies" do @graph.dependencies("a").sort.should == [] end end require 'puppet/util/graph' class Container < Puppet::Type::Component include Puppet::Util::Graph include Enumerable attr_accessor :name def each @children.each do |c| yield c end end def initialize(name, ary) @name = name @children = ary end def push(*ary) ary.each { |c| @children.push(c)} end def to_s @name end def ref "Container[#{self}]" end end require "puppet/resource/catalog" describe "when splicing the graph" do def container_graph @one = Container.new("one", %w{a b}) @two = Container.new("two", ["c", "d"]) @three = Container.new("three", ["i", "j"]) @middle = Container.new("middle", ["e", "f", @two]) @top = Container.new("top", ["g", "h", @middle, @one, @three]) @empty = Container.new("empty", []) @whit = Puppet::Type.type(:whit) @stage = Puppet::Type.type(:stage).new(:name => "foo") @contgraph = @top.to_graph(Puppet::Resource::Catalog.new) # We have to add the container to the main graph, else it won't # be spliced in the dependency graph. @contgraph.add_vertex(@empty) end def containers @contgraph.vertices.select { |x| !x.is_a? String } end def contents_of(x) @contgraph.direct_dependents_of(x) end def dependency_graph @depgraph = Puppet::SimpleGraph.new @contgraph.vertices.each do |v| @depgraph.add_vertex(v) end # We have to specify a relationship to our empty container, else it # never makes it into the dep graph in the first place. @explicit_dependencies = {@one => @two, "f" => "c", "h" => @middle, "c" => @empty} @explicit_dependencies.each do |source, target| @depgraph.add_edge(source, target, :callback => :refresh) end end def splice @contgraph.splice!(@depgraph) end def whit_called(name) x = @depgraph.vertices.find { |v| v.is_a?(@whit) && v.name =~ /#{Regexp.escape(name)}/ } x.should_not be_nil def x.to_s "Whit[#{name}]" end def x.inspect to_s end x end def admissible_sentinel_of(x) @depgraph.vertex?(x) ? x : whit_called("admissible_#{x.ref}") end def completed_sentinel_of(x) @depgraph.vertex?(x) ? x : whit_called("completed_#{x.ref}") end before do container_graph dependency_graph splice end # This is the real heart of splicing -- replacing all containers X in our # relationship graph with a pair of whits { admissible_X and completed_X } # such that that # # 0) completed_X depends on admissible_X # 1) contents of X each depend on admissible_X # 2) completed_X depends on each on the contents of X # 3) everything which depended on X depends on completed_X # 4) admissible_X depends on everything X depended on # 5) the containers and their edges must be removed # # Note that this requires attention to the possible case of containers # which contain or depend on other containers. # # Point by point: # 0) completed_X depends on admissible_X # it "every container's completed sentinel should depend on its admissible sentinel" do containers.each { |container| @depgraph.path_between(admissible_sentinel_of(container),completed_sentinel_of(container)).should be } end # 1) contents of X each depend on admissible_X # it "all contained objects should depend on their container's admissible sentinel" do containers.each { |container| contents_of(container).each { |leaf| @depgraph.should be_edge(admissible_sentinel_of(container),admissible_sentinel_of(leaf)) } } end # 2) completed_X depends on each on the contents of X # it "completed sentinels should depend on their container's contents" do containers.each { |container| contents_of(container).each { |leaf| @depgraph.should be_edge(completed_sentinel_of(leaf),completed_sentinel_of(container)) } } end # # 3) everything which depended on X depends on completed_X # # 4) admissible_X depends on everything X depended on # 5) the containers and their edges must be removed # it "should remove all Container objects from the dependency graph" do @depgraph.vertices.find_all { |v| v.is_a?(Container) }.should be_empty end it "should remove all Stage resources from the dependency graph" do @depgraph.vertices.find_all { |v| v.is_a?(Puppet::Type.type(:stage)) }.should be_empty end it "should no longer contain anything but the non-container objects" do @depgraph.vertices.find_all { |v| ! v.is_a?(String) and ! v.is_a?(@whit)}.should be_empty end it "should retain labels on non-containment edges" do @explicit_dependencies.each { |f,t| @depgraph.edges_between(completed_sentinel_of(f),admissible_sentinel_of(t))[0].label.should == {:callback => :refresh} } end it "should not add labels to edges that have none" do @depgraph.add_edge(@two, @three) splice @depgraph.path_between("c", "i").any? {|segment| segment.all? {|e| e.label == {} }}.should be end it "should copy labels over edges that have none" do @depgraph.add_edge("c", @three, {:callback => :refresh}) splice # And make sure the label got copied. @depgraph.path_between("c", "i").flatten.select {|e| e.label == {:callback => :refresh} }.should_not be_empty end it "should not replace a label with a nil label" do # Lastly, add some new label-less edges and make sure the label stays. @depgraph.add_edge(@middle, @three) @depgraph.add_edge("c", @three, {:callback => :refresh}) splice @depgraph.path_between("c","i").flatten.select {|e| e.label == {:callback => :refresh} }.should_not be_empty end it "should copy labels to all created edges" do @depgraph.add_edge(@middle, @three) @depgraph.add_edge("c", @three, {:callback => :refresh}) splice @three.each do |child| edge = Puppet::Relationship.new("c", child) (path = @depgraph.path_between(edge.source, edge.target)).should be path.should_not be_empty path.flatten.select {|e| e.label == {:callback => :refresh} }.should_not be_empty end end end it "should serialize to YAML using the old format by default" do Puppet::SimpleGraph.use_new_yaml_format.should == false end describe "(yaml tests)" do def empty_graph(graph) end def one_vertex_graph(graph) graph.add_vertex(:a) end def graph_without_edges(graph) [:a, :b, :c].each { |x| graph.add_vertex(x) } end def one_edge_graph(graph) graph.add_edge(:a, :b) end def many_edge_graph(graph) graph.add_edge(:a, :b) graph.add_edge(:a, :c) graph.add_edge(:b, :d) graph.add_edge(:c, :d) end def labeled_edge_graph(graph) graph.add_edge(:a, :b, :callback => :foo, :event => :bar) end def overlapping_edge_graph(graph) graph.add_edge(:a, :b, :callback => :foo, :event => :bar) graph.add_edge(:a, :b, :callback => :biz, :event => :baz) end def self.all_test_graphs [:empty_graph, :one_vertex_graph, :graph_without_edges, :one_edge_graph, :many_edge_graph, :labeled_edge_graph, :overlapping_edge_graph] end def object_ids(enumerable) # Return a sorted list of the object id's of the elements of an # enumerable. enumerable.collect { |x| x.object_id }.sort end def graph_to_yaml(graph, which_format) previous_use_new_yaml_format = Puppet::SimpleGraph.use_new_yaml_format Puppet::SimpleGraph.use_new_yaml_format = (which_format == :new) ZAML.dump(graph) ensure Puppet::SimpleGraph.use_new_yaml_format = previous_use_new_yaml_format end # Test serialization of graph to YAML. [:old, :new].each do |which_format| all_test_graphs.each do |graph_to_test| it "should be able to serialize #{graph_to_test} to YAML (#{which_format} format)" do graph = Puppet::SimpleGraph.new send(graph_to_test, graph) yaml_form = graph_to_yaml(graph, which_format) # Hack the YAML so that objects in the Puppet namespace get # changed to YAML::DomainType objects. This lets us inspect # the serialized objects easily without invoking any # yaml_initialize hooks. yaml_form.gsub!('!ruby/object:Puppet::', '!hack/object:Puppet::') serialized_object = YAML.load(yaml_form) # Check that the object contains instance variables @edges and # @vertices only. @reversal is also permitted, but we don't # check it, because it is going to be phased out. serialized_object.type_id.should == 'object:Puppet::SimpleGraph' serialized_object.value.keys.reject { |x| x == 'reversal' }.sort.should == ['edges', 'vertices'] # Check edges by forming a set of tuples (source, target, # callback, event) based on the graph and the YAML and make sure # they match. edges = serialized_object.value['edges'] edges.should be_a(Array) expected_edge_tuples = graph.edges.collect { |edge| [edge.source, edge.target, edge.callback, edge.event] } actual_edge_tuples = edges.collect do |edge| edge.type_id.should == 'object:Puppet::Relationship' %w{source target}.each { |x| edge.value.keys.should include(x) } edge.value.keys.each { |x| ['source', 'target', 'callback', 'event'].should include(x) } %w{source target callback event}.collect { |x| edge.value[x] } end Set.new(actual_edge_tuples).should == Set.new(expected_edge_tuples) actual_edge_tuples.length.should == expected_edge_tuples.length # Check vertices one by one. vertices = serialized_object.value['vertices'] if which_format == :old vertices.should be_a(Hash) Set.new(vertices.keys).should == Set.new(graph.vertices) vertices.each do |key, value| value.type_id.should == 'object:Puppet::SimpleGraph::VertexWrapper' value.value.keys.sort.should == %w{adjacencies vertex} value.value['vertex'].should equal(key) adjacencies = value.value['adjacencies'] adjacencies.should be_a(Hash) Set.new(adjacencies.keys).should == Set.new([:in, :out]) [:in, :out].each do |direction| adjacencies[direction].should be_a(Hash) expected_adjacent_vertices = Set.new(graph.adjacent(key, :direction => direction, :type => :vertices)) Set.new(adjacencies[direction].keys).should == expected_adjacent_vertices adjacencies[direction].each do |adj_key, adj_value| # Since we already checked edges, just check consistency # with edges. desired_source = direction == :in ? adj_key : key desired_target = direction == :in ? key : adj_key expected_edges = edges.select do |edge| edge.value['source'] == desired_source && edge.value['target'] == desired_target end adj_value.should be_a(Set) if object_ids(adj_value) != object_ids(expected_edges) raise "For vertex #{key.inspect}, direction #{direction.inspect}: expected adjacencies #{expected_edges.inspect} but got #{adj_value.inspect}" end end end end else vertices.should be_a(Array) Set.new(vertices).should == Set.new(graph.vertices) vertices.length.should == graph.vertices.length end end end # Test deserialization of graph from YAML. This presumes the # correctness of serialization to YAML, which has already been # tested. all_test_graphs.each do |graph_to_test| it "should be able to deserialize #{graph_to_test} from YAML (#{which_format} format)" do reference_graph = Puppet::SimpleGraph.new send(graph_to_test, reference_graph) yaml_form = graph_to_yaml(reference_graph, which_format) recovered_graph = YAML.load(yaml_form) # Test that the recovered vertices match the vertices in the # reference graph. expected_vertices = reference_graph.vertices.to_a recovered_vertices = recovered_graph.vertices.to_a Set.new(recovered_vertices).should == Set.new(expected_vertices) recovered_vertices.length.should == expected_vertices.length # Test that the recovered edges match the edges in the # reference graph. expected_edge_tuples = reference_graph.edges.collect do |edge| [edge.source, edge.target, edge.callback, edge.event] end recovered_edge_tuples = recovered_graph.edges.collect do |edge| [edge.source, edge.target, edge.callback, edge.event] end Set.new(recovered_edge_tuples).should == Set.new(expected_edge_tuples) recovered_edge_tuples.length.should == expected_edge_tuples.length # We ought to test that the recovered graph is self-consistent # too. But we're not going to bother with that yet because # the internal representation of the graph is about to change. end end it "should be able to serialize a graph where the vertices contain backreferences to the graph (#{which_format} format)" do reference_graph = Puppet::SimpleGraph.new vertex = Object.new vertex.instance_eval { @graph = reference_graph } reference_graph.add_edge(vertex, :other_vertex) yaml_form = graph_to_yaml(reference_graph, which_format) recovered_graph = YAML.load(yaml_form) recovered_graph.vertices.length.should == 2 recovered_vertex = recovered_graph.vertices.reject { |x| x.is_a?(Symbol) }[0] recovered_vertex.instance_eval { @graph }.should equal(recovered_graph) recovered_graph.edges.length.should == 1 recovered_edge = recovered_graph.edges[0] recovered_edge.source.should equal(recovered_vertex) recovered_edge.target.should == :other_vertex end end it "should serialize properly when used as a base class" do class Puppet::TestDerivedClass < Puppet::SimpleGraph attr_accessor :foo end derived = Puppet::TestDerivedClass.new derived.add_edge(:a, :b) derived.foo = 1234 recovered_derived = YAML.load(YAML.dump(derived)) recovered_derived.class.should equal(Puppet::TestDerivedClass) recovered_derived.edges.length.should == 1 recovered_derived.edges[0].source.should == :a recovered_derived.edges[0].target.should == :b recovered_derived.vertices.length.should == 2 recovered_derived.foo.should == 1234 end end end diff --git a/spec/unit/ssl/certificate_authority/interface_spec.rb b/spec/unit/ssl/certificate_authority/interface_spec.rb index dd442104c..3e612161d 100755 --- a/spec/unit/ssl/certificate_authority/interface_spec.rb +++ b/spec/unit/ssl/certificate_authority/interface_spec.rb @@ -1,375 +1,375 @@ -#!/usr/bin/env rspec +#!/usr/bin/env ruby -S rspec require 'spec_helper' require 'puppet/ssl/certificate_authority' shared_examples_for "a normal interface method" do it "should call the method on the CA for each host specified if an array was provided" do @ca.expects(@method).with("host1") @ca.expects(@method).with("host2") @applier = Puppet::SSL::CertificateAuthority::Interface.new(@method, :to => %w{host1 host2}) @applier.apply(@ca) end it "should call the method on the CA for all existing certificates if :all was provided" do @ca.expects(:list).returns %w{host1 host2} @ca.expects(@method).with("host1") @ca.expects(@method).with("host2") @applier = Puppet::SSL::CertificateAuthority::Interface.new(@method, :to => :all) @applier.apply(@ca) end end describe Puppet::SSL::CertificateAuthority::Interface do before do @class = Puppet::SSL::CertificateAuthority::Interface end describe "when initializing" do it "should set its method using its settor" do instance = @class.new(:generate, :to => :all) instance.method.should == :generate end it "should set its subjects using the settor" do instance = @class.new(:generate, :to => :all) instance.subjects.should == :all end it "should set the digest if given" do interface = @class.new(:generate, :to => :all, :digest => :digest) interface.digest.should == :digest end it "should set the digest to md5 if none given" do interface = @class.new(:generate, :to => :all) interface.digest.should == :MD5 end end describe "when setting the method" do it "should set the method" do instance = @class.new(:generate, :to => :all) instance.method = :list instance.method.should == :list end it "should fail if the method isn't a member of the INTERFACE_METHODS array" do lambda { @class.new(:thing, :to => :all) }.should raise_error(ArgumentError, /Invalid method thing to apply/) end end describe "when setting the subjects" do it "should set the subjects" do instance = @class.new(:generate, :to => :all) instance.subjects = :signed instance.subjects.should == :signed end it "should fail if the subjects setting isn't :all or an array" do lambda { @class.new(:generate, :to => "other") }.should raise_error(ArgumentError, /Subjects must be an array or :all; not other/) end end it "should have a method for triggering the application" do @class.new(:generate, :to => :all).should respond_to(:apply) end describe "when applying" do before do # We use a real object here, because :verify can't be stubbed, apparently. @ca = Object.new end it "should raise InterfaceErrors" do @applier = @class.new(:revoke, :to => :all) @ca.expects(:list).raises Puppet::SSL::CertificateAuthority::Interface::InterfaceError lambda { @applier.apply(@ca) }.should raise_error(Puppet::SSL::CertificateAuthority::Interface::InterfaceError) end - it "should log non-Interface failures rather than failing" do + it "should log non-Interface failures" do @applier = @class.new(:revoke, :to => :all) @ca.expects(:list).raises ArgumentError Puppet.expects(:err) - lambda { @applier.apply(@ca) }.should_not raise_error + lambda { @applier.apply(@ca) }.should raise_error end describe "with an empty array specified and the method is not list" do it "should fail" do @applier = @class.new(:sign, :to => []) lambda { @applier.apply(@ca) }.should raise_error(ArgumentError) end end describe ":generate" do it "should fail if :all was specified" do @applier = @class.new(:generate, :to => :all) lambda { @applier.apply(@ca) }.should raise_error(ArgumentError) end it "should call :generate on the CA for each host specified" do @applier = @class.new(:generate, :to => %w{host1 host2}) @ca.expects(:generate).with("host1", {}) @ca.expects(:generate).with("host2", {}) @applier.apply(@ca) end end describe ":verify" do before { @method = :verify } #it_should_behave_like "a normal interface method" it "should call the method on the CA for each host specified if an array was provided" do # LAK:NOTE Mocha apparently doesn't allow you to mock :verify, but I'm confident this works in real life. end it "should call the method on the CA for all existing certificates if :all was provided" do # LAK:NOTE Mocha apparently doesn't allow you to mock :verify, but I'm confident this works in real life. end end describe ":destroy" do before { @method = :destroy } it_should_behave_like "a normal interface method" end describe ":revoke" do before { @method = :revoke } it_should_behave_like "a normal interface method" end describe ":sign" do describe "and an array of names was provided" do let(:applier) { @class.new(:sign, @options.merge(:to => %w{host1 host2})) } it "should sign the specified waiting certificate requests" do @options = {:allow_dns_alt_names => false} @ca.expects(:sign).with("host1", false) @ca.expects(:sign).with("host2", false) applier.apply(@ca) end it "should sign the certificate requests with alt names if specified" do @options = {:allow_dns_alt_names => true} @ca.expects(:sign).with("host1", true) @ca.expects(:sign).with("host2", true) applier.apply(@ca) end end describe "and :all was provided" do it "should sign all waiting certificate requests" do @ca.stubs(:waiting?).returns(%w{cert1 cert2}) @ca.expects(:sign).with("cert1", nil) @ca.expects(:sign).with("cert2", nil) @applier = @class.new(:sign, :to => :all) @applier.apply(@ca) end it "should fail if there are no waiting certificate requests" do @ca.stubs(:waiting?).returns([]) @applier = @class.new(:sign, :to => :all) lambda { @applier.apply(@ca) }.should raise_error(Puppet::SSL::CertificateAuthority::Interface::InterfaceError) end end end describe ":list" do before :each do @cert = Puppet::SSL::Certificate.new 'foo' @csr = Puppet::SSL::CertificateRequest.new 'bar' @cert.stubs(:subject_alt_names).returns [] @csr.stubs(:subject_alt_names).returns [] Puppet::SSL::Certificate.indirection.stubs(:find).returns @cert Puppet::SSL::CertificateRequest.indirection.stubs(:find).returns @csr @ca.expects(:waiting?).returns %w{host1 host2 host3} @ca.expects(:list).returns %w{host4 host5 host6} @ca.stubs(:fingerprint).returns "fingerprint" @ca.stubs(:verify) end describe "and an empty array was provided" do it "should print all certificate requests" do applier = @class.new(:list, :to => []) applier.expects(:puts).with(<<-OUTPUT.chomp) "host1" (fingerprint) "host2" (fingerprint) "host3" (fingerprint) OUTPUT applier.apply(@ca) end end describe "and :all was provided" do it "should print a string containing all certificate requests and certificates" do @ca.stubs(:verify).with("host4").raises(Puppet::SSL::CertificateAuthority::CertificateVerificationError.new(23), "certificate revoked") applier = @class.new(:list, :to => :all) applier.expects(:puts).with(<<-OUTPUT.chomp) "host1" (fingerprint) "host2" (fingerprint) "host3" (fingerprint) + "host5" (fingerprint) + "host6" (fingerprint) - "host4" (fingerprint) (certificate revoked) OUTPUT applier.apply(@ca) end end describe "and :signed was provided" do it "should print a string containing all signed certificate requests and certificates" do applier = @class.new(:list, :to => :signed) applier.expects(:puts).with(<<-OUTPUT.chomp) + "host4" (fingerprint) + "host5" (fingerprint) + "host6" (fingerprint) OUTPUT applier.apply(@ca) end it "should include subject alt names if they are on the certificate request" do @csr.stubs(:subject_alt_names).returns ["DNS:foo", "DNS:bar"] applier = @class.new(:list, :to => ['host1']) applier.expects(:puts).with(<<-OUTPUT.chomp) "host1" (fingerprint) (alt names: "DNS:foo", "DNS:bar") OUTPUT applier.apply(@ca) end end describe "and an array of names was provided" do it "should print all named hosts" do applier = @class.new(:list, :to => %w{host1 host2 host4 host5}) applier.expects(:puts).with(<<-OUTPUT.chomp) "host1" (fingerprint) "host2" (fingerprint) + "host4" (fingerprint) + "host5" (fingerprint) OUTPUT applier.apply(@ca) end end end describe ":print" do describe "and :all was provided" do it "should print all certificates" do @ca.expects(:list).returns %w{host1 host2} @applier = @class.new(:print, :to => :all) @ca.expects(:print).with("host1").returns "h1" @applier.expects(:puts).with "h1" @ca.expects(:print).with("host2").returns "h2" @applier.expects(:puts).with "h2" @applier.apply(@ca) end end describe "and an array of names was provided" do it "should print each named certificate if found" do @applier = @class.new(:print, :to => %w{host1 host2}) @ca.expects(:print).with("host1").returns "h1" @applier.expects(:puts).with "h1" @ca.expects(:print).with("host2").returns "h2" @applier.expects(:puts).with "h2" @applier.apply(@ca) end it "should log any named but not found certificates" do @applier = @class.new(:print, :to => %w{host1 host2}) @ca.expects(:print).with("host1").returns "h1" @applier.expects(:puts).with "h1" @ca.expects(:print).with("host2").returns nil Puppet.expects(:err).with { |msg| msg.include?("host2") } @applier.apply(@ca) end end end describe ":fingerprint" do it "should fingerprint with the set digest algorithm" do @applier = @class.new(:fingerprint, :to => %w{host1}, :digest => :digest) @ca.expects(:fingerprint).with("host1", :digest).returns "fingerprint1" @applier.expects(:puts).with "host1 fingerprint1" @applier.apply(@ca) end describe "and :all was provided" do it "should fingerprint all certificates (including waiting ones)" do @ca.expects(:list).returns %w{host1} @ca.expects(:waiting?).returns %w{host2} @applier = @class.new(:fingerprint, :to => :all) @ca.expects(:fingerprint).with("host1", :MD5).returns "fingerprint1" @applier.expects(:puts).with "host1 fingerprint1" @ca.expects(:fingerprint).with("host2", :MD5).returns "fingerprint2" @applier.expects(:puts).with "host2 fingerprint2" @applier.apply(@ca) end end describe "and an array of names was provided" do it "should print each named certificate if found" do @applier = @class.new(:fingerprint, :to => %w{host1 host2}) @ca.expects(:fingerprint).with("host1", :MD5).returns "fingerprint1" @applier.expects(:puts).with "host1 fingerprint1" @ca.expects(:fingerprint).with("host2", :MD5).returns "fingerprint2" @applier.expects(:puts).with "host2 fingerprint2" @applier.apply(@ca) end end end end end diff --git a/spec/unit/ssl/certificate_authority_spec.rb b/spec/unit/ssl/certificate_authority_spec.rb index 7fddeb3bc..c99491166 100755 --- a/spec/unit/ssl/certificate_authority_spec.rb +++ b/spec/unit/ssl/certificate_authority_spec.rb @@ -1,947 +1,948 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/ssl/certificate_authority' describe Puppet::SSL::CertificateAuthority do after do Puppet::SSL::CertificateAuthority.instance_variable_set(:@singleton_instance, nil) Puppet.settings.clearused end def stub_ca_host @key = mock 'key' @key.stubs(:content).returns "cakey" @cacert = mock 'certificate' @cacert.stubs(:content).returns "cacertificate" @host = stub 'ssl_host', :key => @key, :certificate => @cacert, :name => Puppet::SSL::Host.ca_name end it "should have a class method for returning a singleton instance" do Puppet::SSL::CertificateAuthority.should respond_to(:instance) end describe "when finding an existing instance" do describe "and the host is a CA host and the run_mode is master" do before do Puppet[:ca] = true Puppet.run_mode.stubs(:master?).returns true @ca = mock('ca') Puppet::SSL::CertificateAuthority.stubs(:new).returns @ca end it "should return an instance" do Puppet::SSL::CertificateAuthority.instance.should equal(@ca) end it "should always return the same instance" do Puppet::SSL::CertificateAuthority.instance.should equal(Puppet::SSL::CertificateAuthority.instance) end end describe "and the host is not a CA host" do it "should return nil" do Puppet.settings.stubs(:value).with(:ca).returns false Puppet.run_mode.stubs(:master?).returns true ca = mock('ca') Puppet::SSL::CertificateAuthority.expects(:new).never Puppet::SSL::CertificateAuthority.instance.should be_nil end end describe "and the run_mode is not master" do it "should return nil" do Puppet.settings.stubs(:value).with(:ca).returns true Puppet.run_mode.stubs(:master?).returns false ca = mock('ca') Puppet::SSL::CertificateAuthority.expects(:new).never Puppet::SSL::CertificateAuthority.instance.should be_nil end end end describe "when initializing" do before do Puppet.settings.stubs(:use) Puppet.settings.stubs(:value).returns "ca_testing" Puppet::SSL::CertificateAuthority.any_instance.stubs(:setup) end it "should always set its name to the value of :certname" do Puppet.settings.expects(:value).with(:certname).returns "ca_testing" Puppet::SSL::CertificateAuthority.new.name.should == "ca_testing" end it "should create an SSL::Host instance whose name is the 'ca_name'" do Puppet::SSL::Host.expects(:ca_name).returns "caname" host = stub 'host' Puppet::SSL::Host.expects(:new).with("caname").returns host Puppet::SSL::CertificateAuthority.new end it "should use the :main, :ca, and :ssl settings sections" do Puppet.settings.expects(:use).with(:main, :ssl, :ca) Puppet::SSL::CertificateAuthority.new end it "should create an inventory instance" do Puppet::SSL::Inventory.expects(:new).returns "inventory" Puppet::SSL::CertificateAuthority.new.inventory.should == "inventory" end it "should make sure the CA is set up" do Puppet::SSL::CertificateAuthority.any_instance.expects(:setup) Puppet::SSL::CertificateAuthority.new end end describe "when setting itself up" do it "should generate the CA certificate if it does not have one" do Puppet.settings.stubs :use host = stub 'host' Puppet::SSL::Host.stubs(:new).returns host host.expects(:certificate).returns nil Puppet::SSL::CertificateAuthority.any_instance.expects(:generate_ca_certificate) Puppet::SSL::CertificateAuthority.new end end describe "when retrieving the certificate revocation list" do before do Puppet.settings.stubs(:use) Puppet.settings.stubs(:value).returns "ca_testing" Puppet.settings.stubs(:value).with(:cacrl).returns "/my/crl" cert = stub("certificate", :content => "real_cert") key = stub("key", :content => "real_key") @host = stub 'host', :certificate => cert, :name => "hostname", :key => key Puppet::SSL::CertificateAuthority.any_instance.stubs(:setup) @ca = Puppet::SSL::CertificateAuthority.new @ca.stubs(:host).returns @host end it "should return any found CRL instance" do crl = mock 'crl' Puppet::SSL::CertificateRevocationList.indirection.expects(:find).returns crl @ca.crl.should equal(crl) end it "should create, generate, and save a new CRL instance of no CRL can be found" do crl = Puppet::SSL::CertificateRevocationList.new("fakename") Puppet::SSL::CertificateRevocationList.indirection.expects(:find).returns nil Puppet::SSL::CertificateRevocationList.expects(:new).returns crl crl.expects(:generate).with(@ca.host.certificate.content, @ca.host.key.content) Puppet::SSL::CertificateRevocationList.indirection.expects(:save).with(crl) @ca.crl.should equal(crl) end end describe "when generating a self-signed CA certificate" do before do Puppet.settings.stubs(:use) Puppet.settings.stubs(:value).returns "ca_testing" Puppet::SSL::CertificateAuthority.any_instance.stubs(:setup) Puppet::SSL::CertificateAuthority.any_instance.stubs(:crl) @ca = Puppet::SSL::CertificateAuthority.new @host = stub 'host', :key => mock("key"), :name => "hostname", :certificate => mock('certificate') Puppet::SSL::CertificateRequest.any_instance.stubs(:generate) @ca.stubs(:host).returns @host end it "should create and store a password at :capass" do Puppet.settings.expects(:value).with(:capass).returns "/path/to/pass" FileTest.expects(:exist?).with("/path/to/pass").returns false fh = mock 'filehandle' Puppet.settings.expects(:write).with(:capass).yields fh fh.expects(:print).with { |s| s.length > 18 } @ca.stubs(:sign) @ca.generate_ca_certificate end it "should generate a key if one does not exist" do @ca.stubs :generate_password @ca.stubs :sign @ca.host.expects(:key).returns nil @ca.host.expects(:generate_key) @ca.generate_ca_certificate end it "should create and sign a self-signed cert using the CA name" do request = mock 'request' Puppet::SSL::CertificateRequest.expects(:new).with(@ca.host.name).returns request request.expects(:generate).with(@ca.host.key) request.stubs(:request_extensions => []) @ca.expects(:sign).with(@host.name, false, request) @ca.stubs :generate_password @ca.generate_ca_certificate end it "should generate its CRL" do @ca.stubs :generate_password @ca.stubs :sign @ca.host.expects(:key).returns nil @ca.host.expects(:generate_key) @ca.expects(:crl) @ca.generate_ca_certificate end end describe "when signing" do before do Puppet.settings.stubs(:use) Puppet::SSL::CertificateAuthority.any_instance.stubs(:password?).returns true stub_ca_host Puppet::SSL::Host.expects(:new).with(Puppet::SSL::Host.ca_name).returns @host @ca = Puppet::SSL::CertificateAuthority.new @name = "myhost" @real_cert = stub 'realcert', :sign => nil @cert = Puppet::SSL::Certificate.new(@name) @cert.content = @real_cert Puppet::SSL::Certificate.stubs(:new).returns @cert @cert.stubs(:content=) Puppet::SSL::Certificate.indirection.stubs(:save) # Stub out the factory Puppet::SSL::CertificateFactory.stubs(:build).returns "my real cert" @request_content = stub "request content stub", :subject => OpenSSL::X509::Name.new([['CN', @name]]) @request = stub 'request', :name => @name, :request_extensions => [], :subject_alt_names => [], :content => @request_content # And the inventory @inventory = stub 'inventory', :add => nil @ca.stubs(:inventory).returns @inventory Puppet::SSL::CertificateRequest.indirection.stubs(:destroy) end describe "and calculating the next certificate serial number" do before do @path = "/path/to/serial" Puppet.settings.stubs(:value).with(:serial).returns @path @filehandle = stub 'filehandle', :<< => @filehandle Puppet.settings.stubs(:readwritelock).with(:serial).yields @filehandle end it "should default to 0x1 for the first serial number" do @ca.next_serial.should == 0x1 end it "should return the current content of the serial file" do FileTest.stubs(:exist?).with(@path).returns true File.expects(:read).with(@path).returns "0002" @ca.next_serial.should == 2 end it "should write the next serial number to the serial file as hex" do @filehandle.expects(:<<).with("0002") @ca.next_serial end it "should lock the serial file while writing" do Puppet.settings.expects(:readwritelock).with(:serial) @ca.next_serial end end describe "its own certificate" do before do @serial = 10 @ca.stubs(:next_serial).returns @serial end it "should not look up a certificate request for the host" do Puppet::SSL::CertificateRequest.indirection.expects(:find).never @ca.sign(@name, true, @request) end it "should use a certificate type of :ca" do Puppet::SSL::CertificateFactory.expects(:build).with do |*args| args[0].should == :ca end.returns "my real cert" @ca.sign(@name, :ca, @request) end it "should pass the provided CSR as the CSR" do Puppet::SSL::CertificateFactory.expects(:build).with do |*args| args[1].should == @request end.returns "my real cert" @ca.sign(@name, :ca, @request) end it "should use the provided CSR's content as the issuer" do Puppet::SSL::CertificateFactory.expects(:build).with do |*args| args[2].subject.to_s.should == "/CN=myhost" end.returns "my real cert" @ca.sign(@name, :ca, @request) end it "should pass the next serial as the serial number" do Puppet::SSL::CertificateFactory.expects(:build).with do |*args| args[3].should == @serial end.returns "my real cert" @ca.sign(@name, :ca, @request) end it "should sign the certificate request even if it contains alt names" do @request.stubs(:subject_alt_names).returns %w[DNS:foo DNS:bar DNS:baz] expect do @ca.sign(@name, false, @request) end.should_not raise_error(Puppet::SSL::CertificateAuthority::CertificateSigningError) end it "should save the resulting certificate" do Puppet::SSL::Certificate.indirection.expects(:save).with(@cert) @ca.sign(@name, :ca, @request) end end describe "another host's certificate" do before do @serial = 10 @ca.stubs(:next_serial).returns @serial Puppet::SSL::CertificateRequest.indirection.stubs(:find).with(@name).returns @request Puppet::SSL::CertificateRequest.indirection.stubs :save end it "should use a certificate type of :server" do Puppet::SSL::CertificateFactory.expects(:build).with do |*args| args[0] == :server end.returns "my real cert" @ca.sign(@name) end it "should use look up a CSR for the host in the :ca_file terminus" do Puppet::SSL::CertificateRequest.indirection.expects(:find).with(@name).returns @request @ca.sign(@name) end it "should fail if no CSR can be found for the host" do Puppet::SSL::CertificateRequest.indirection.expects(:find).with(@name).returns nil - lambda { @ca.sign(@name) }.should raise_error(ArgumentError) + expect { @ca.sign(@name) }.to raise_error(ArgumentError) end it "should fail if an unknown request extension is present" do @request.stubs :request_extensions => [{ "oid" => "bananas", "value" => "delicious" }] - expect { @ca.sign(@name) }. - should raise_error(/CSR has request extensions that are not permitted/) + expect { + @ca.sign(@name) + }.to raise_error(/CSR has request extensions that are not permitted/) end it "should fail if the CSR contains alt names and they are not expected" do @request.stubs(:subject_alt_names).returns %w[DNS:foo DNS:bar DNS:baz] expect do @ca.sign(@name, false) end.to raise_error(Puppet::SSL::CertificateAuthority::CertificateSigningError, /CSR '#{@name}' contains subject alternative names \(.*?\), which are disallowed. Use `puppet cert --allow-dns-alt-names sign #{@name}` to sign this request./) end it "should not fail if the CSR does not contain alt names and they are expected" do @request.stubs(:subject_alt_names).returns [] - expect { @ca.sign(@name, true) }.should_not raise_error + expect { @ca.sign(@name, true) }.to_not raise_error end it "should reject alt names by default" do @request.stubs(:subject_alt_names).returns %w[DNS:foo DNS:bar DNS:baz] expect do @ca.sign(@name) end.to raise_error(Puppet::SSL::CertificateAuthority::CertificateSigningError, /CSR '#{@name}' contains subject alternative names \(.*?\), which are disallowed. Use `puppet cert --allow-dns-alt-names sign #{@name}` to sign this request./) end it "should use the CA certificate as the issuer" do Puppet::SSL::CertificateFactory.expects(:build).with do |*args| args[2] == @cacert.content end.returns "my real cert" @ca.sign(@name) end it "should pass the next serial as the serial number" do Puppet::SSL::CertificateFactory.expects(:build).with do |*args| args[3] == @serial end.returns "my real cert" @ca.sign(@name) end it "should sign the resulting certificate using its real key and a digest" do digest = mock 'digest' OpenSSL::Digest::SHA1.expects(:new).returns digest key = stub 'key', :content => "real_key" @ca.host.stubs(:key).returns key @cert.content.expects(:sign).with("real_key", digest) @ca.sign(@name) end it "should save the resulting certificate" do Puppet::SSL::Certificate.indirection.stubs(:save).with(@cert) @ca.sign(@name) end it "should remove the host's certificate request" do Puppet::SSL::CertificateRequest.indirection.expects(:destroy).with(@name) @ca.sign(@name) end it "should check the internal signing policies" do @ca.expects(:check_internal_signing_policies).returns true @ca.sign(@name) end end context "#check_internal_signing_policies" do before do @serial = 10 @ca.stubs(:next_serial).returns @serial Puppet::SSL::CertificateRequest.indirection.stubs(:find).with(@name).returns @request @cert.stubs :save end it "should reject CSRs whose CN doesn't match the name for which we're signing them" do # Shorten this so the test doesn't take too long Puppet[:keylength] = 1024 key = Puppet::SSL::Key.new('the_certname') key.generate csr = Puppet::SSL::CertificateRequest.new('the_certname') csr.generate(key) expect do @ca.check_internal_signing_policies('not_the_certname', csr, false) end.to raise_error( Puppet::SSL::CertificateAuthority::CertificateSigningError, /common name "the_certname" does not match expected certname "not_the_certname"/ ) end describe "when validating the CN" do before :all do Puppet[:keylength] = 1024 @signing_key = Puppet::SSL::Key.new('my_signing_key') @signing_key.generate end [ 'completely_okay', 'sure, why not? :)', 'so+many(things)-are=allowed.', 'this"is#just&madness%you[see]', 'and even a (an?) \\!', 'waltz, nymph, for quick jigs vex bud.', '{552c04ca-bb1b-11e1-874b-60334b04494e}' ].each do |name| it "should accept #{name.inspect}" do csr = Puppet::SSL::CertificateRequest.new(name) csr.generate(@signing_key) @ca.check_internal_signing_policies(name, csr, false) end end [ 'super/bad', "not\neven\tkind\rof", "ding\adong\a", "hidden\b\b\b\b\b\bmessage", "☃ :(" ].each do |name| it "should reject #{name.inspect}" do # We aren't even allowed to make objects with these names, so let's # stub that to simulate an invalid one coming from outside Puppet Puppet::SSL::CertificateRequest.stubs(:validate_certname) csr = Puppet::SSL::CertificateRequest.new(name) csr.generate(@signing_key) expect do @ca.check_internal_signing_policies(name, csr, false) end.to raise_error( Puppet::SSL::CertificateAuthority::CertificateSigningError, /subject contains unprintable or non-ASCII characters/ ) end end end it "should reject a critical extension that isn't on the whitelist" do @request.stubs(:request_extensions).returns [{ "oid" => "banana", "value" => "yumm", "critical" => true }] expect { @ca.check_internal_signing_policies(@name, @request, false) }.to raise_error( Puppet::SSL::CertificateAuthority::CertificateSigningError, /request extensions that are not permitted/ ) end it "should reject a non-critical extension that isn't on the whitelist" do @request.stubs(:request_extensions).returns [{ "oid" => "peach", "value" => "meh", "critical" => false }] expect { @ca.check_internal_signing_policies(@name, @request, false) }.to raise_error( Puppet::SSL::CertificateAuthority::CertificateSigningError, /request extensions that are not permitted/ ) end it "should reject non-whitelist extensions even if a valid extension is present" do @request.stubs(:request_extensions).returns [{ "oid" => "peach", "value" => "meh", "critical" => false }, { "oid" => "subjectAltName", "value" => "DNS:foo", "critical" => true }] expect { @ca.check_internal_signing_policies(@name, @request, false) }.to raise_error( Puppet::SSL::CertificateAuthority::CertificateSigningError, /request extensions that are not permitted/ ) end it "should reject a subjectAltName for a non-DNS value" do @request.stubs(:subject_alt_names).returns ['DNS:foo', 'email:bar@example.com'] expect { @ca.check_internal_signing_policies(@name, @request, true) }.to raise_error( Puppet::SSL::CertificateAuthority::CertificateSigningError, /subjectAltName outside the DNS label space/ ) end it "should reject a wildcard subject" do @request.content.stubs(:subject). returns(OpenSSL::X509::Name.new([["CN", "*.local"]])) expect { @ca.check_internal_signing_policies('*.local', @request, false) }.to raise_error( Puppet::SSL::CertificateAuthority::CertificateSigningError, /subject contains a wildcard/ ) end it "should reject a wildcard subjectAltName" do @request.stubs(:subject_alt_names).returns ['DNS:foo', 'DNS:*.bar'] expect { @ca.check_internal_signing_policies(@name, @request, true) }.to raise_error( Puppet::SSL::CertificateAuthority::CertificateSigningError, /subjectAltName contains a wildcard/ ) end end it "should create a certificate instance with the content set to the newly signed x509 certificate" do @serial = 10 @ca.stubs(:next_serial).returns @serial Puppet::SSL::CertificateRequest.indirection.stubs(:find).with(@name).returns @request Puppet::SSL::Certificate.indirection.stubs :save Puppet::SSL::Certificate.expects(:new).with(@name).returns @cert @ca.sign(@name) end it "should return the certificate instance" do @ca.stubs(:next_serial).returns @serial Puppet::SSL::CertificateRequest.indirection.stubs(:find).with(@name).returns @request Puppet::SSL::Certificate.indirection.stubs :save @ca.sign(@name).should equal(@cert) end it "should add the certificate to its inventory" do @ca.stubs(:next_serial).returns @serial @inventory.expects(:add).with(@cert) Puppet::SSL::CertificateRequest.indirection.stubs(:find).with(@name).returns @request Puppet::SSL::Certificate.indirection.stubs :save @ca.sign(@name) end it "should have a method for triggering autosigning of available CSRs" do @ca.should respond_to(:autosign) end describe "when autosigning certificates" do it "should do nothing if autosign is disabled" do Puppet.settings.expects(:value).with(:autosign).returns 'false' Puppet::SSL::CertificateRequest.indirection.expects(:search).never @ca.autosign end it "should do nothing if no autosign.conf exists" do Puppet.settings.expects(:value).with(:autosign).returns '/auto/sign' FileTest.expects(:exist?).with("/auto/sign").returns false Puppet::SSL::CertificateRequest.indirection.expects(:search).never @ca.autosign end describe "and autosign is enabled and the autosign.conf file exists" do before do Puppet.settings.stubs(:value).with(:autosign).returns '/auto/sign' FileTest.stubs(:exist?).with("/auto/sign").returns true File.stubs(:readlines).with("/auto/sign").returns ["one\n", "two\n"] Puppet::SSL::CertificateRequest.indirection.stubs(:search).returns [] @store = stub 'store', :allow => nil Puppet::Network::AuthStore.stubs(:new).returns @store end describe "when creating the AuthStore instance to verify autosigning" do it "should create an AuthStore with each line in the configuration file allowed to be autosigned" do Puppet::Network::AuthStore.expects(:new).returns @store @store.expects(:allow).with("one") @store.expects(:allow).with("two") @ca.autosign end it "should reparse the autosign configuration on each call" do Puppet::Network::AuthStore.expects(:new).times(2).returns @store @ca.autosign @ca.autosign end it "should ignore comments" do File.stubs(:readlines).with("/auto/sign").returns ["one\n", "#two\n"] @store.expects(:allow).with("one") @ca.autosign end it "should ignore blank lines" do File.stubs(:readlines).with("/auto/sign").returns ["one\n", "\n"] @store.expects(:allow).with("one") @ca.autosign end end it "should sign all CSRs whose hostname matches the autosign configuration" do csr1 = mock 'csr1' csr2 = mock 'csr2' Puppet::SSL::CertificateRequest.indirection.stubs(:search).returns [csr1, csr2] end it "should not sign CSRs whose hostname does not match the autosign configuration" do csr1 = mock 'csr1' csr2 = mock 'csr2' Puppet::SSL::CertificateRequest.indirection.stubs(:search).returns [csr1, csr2] end end end end describe "when managing certificate clients" do before do Puppet.settings.stubs(:use) Puppet::SSL::CertificateAuthority.any_instance.stubs(:password?).returns true stub_ca_host Puppet::SSL::Host.expects(:new).returns @host Puppet::SSL::CertificateAuthority.any_instance.stubs(:host).returns @host @cacert = mock 'certificate' @cacert.stubs(:content).returns "cacertificate" @ca = Puppet::SSL::CertificateAuthority.new end it "should have a method for acting on the SSL files" do @ca.should respond_to(:apply) end describe "when applying a method to a set of hosts" do it "should fail if no subjects have been specified" do - lambda { @ca.apply(:generate) }.should raise_error(ArgumentError) + expect { @ca.apply(:generate) }.to raise_error(ArgumentError) end it "should create an Interface instance with the specified method and the options" do Puppet::SSL::CertificateAuthority::Interface.expects(:new).with(:generate, :to => :host).returns(stub('applier', :apply => nil)) @ca.apply(:generate, :to => :host) end it "should apply the Interface with itself as the argument" do applier = stub('applier') applier.expects(:apply).with(@ca) Puppet::SSL::CertificateAuthority::Interface.expects(:new).returns applier @ca.apply(:generate, :to => :ca_testing) end end it "should be able to list waiting certificate requests" do req1 = stub 'req1', :name => "one" req2 = stub 'req2', :name => "two" Puppet::SSL::CertificateRequest.indirection.expects(:search).with("*").returns [req1, req2] @ca.waiting?.should == %w{one two} end it "should delegate removing hosts to the Host class" do Puppet::SSL::Host.expects(:destroy).with("myhost") @ca.destroy("myhost") end it "should be able to verify certificates" do @ca.should respond_to(:verify) end it "should list certificates as the sorted list of all existing signed certificates" do cert1 = stub 'cert1', :name => "cert1" cert2 = stub 'cert2', :name => "cert2" Puppet::SSL::Certificate.indirection.expects(:search).with("*").returns [cert1, cert2] @ca.list.should == %w{cert1 cert2} end describe "and printing certificates" do it "should return nil if the certificate cannot be found" do Puppet::SSL::Certificate.indirection.expects(:find).with("myhost").returns nil @ca.print("myhost").should be_nil end it "should print certificates by calling :to_text on the host's certificate" do cert1 = stub 'cert1', :name => "cert1", :to_text => "mytext" Puppet::SSL::Certificate.indirection.expects(:find).with("myhost").returns cert1 @ca.print("myhost").should == "mytext" end end describe "and fingerprinting certificates" do before :each do @cert = stub 'cert', :name => "cert", :fingerprint => "DIGEST" Puppet::SSL::Certificate.indirection.stubs(:find).with("myhost").returns @cert Puppet::SSL::CertificateRequest.indirection.stubs(:find).with("myhost") end it "should raise an error if the certificate or CSR cannot be found" do Puppet::SSL::Certificate.indirection.expects(:find).with("myhost").returns nil Puppet::SSL::CertificateRequest.indirection.expects(:find).with("myhost").returns nil - lambda { @ca.fingerprint("myhost") }.should raise_error + expect { @ca.fingerprint("myhost") }.to raise_error end it "should try to find a CSR if no certificate can be found" do Puppet::SSL::Certificate.indirection.expects(:find).with("myhost").returns nil Puppet::SSL::CertificateRequest.indirection.expects(:find).with("myhost").returns @cert @cert.expects(:fingerprint) @ca.fingerprint("myhost") end it "should delegate to the certificate fingerprinting" do @cert.expects(:fingerprint) @ca.fingerprint("myhost") end it "should propagate the digest algorithm to the certificate fingerprinting system" do @cert.expects(:fingerprint).with(:digest) @ca.fingerprint("myhost", :digest) end end describe "and verifying certificates" do before do @store = stub 'store', :verify => true, :add_file => nil, :purpose= => nil, :add_crl => true, :flags= => nil OpenSSL::X509::Store.stubs(:new).returns @store Puppet.settings.stubs(:value).returns "crtstuff" @cert = stub 'cert', :content => "mycert" Puppet::SSL::Certificate.indirection.stubs(:find).returns @cert @crl = stub('crl', :content => "mycrl") @ca.stubs(:crl).returns @crl end it "should fail if the host's certificate cannot be found" do Puppet::SSL::Certificate.indirection.expects(:find).with("me").returns(nil) - lambda { @ca.verify("me") }.should raise_error(ArgumentError) + expect { @ca.verify("me") }.to raise_error(ArgumentError) end it "should create an SSL Store to verify" do OpenSSL::X509::Store.expects(:new).returns @store @ca.verify("me") end it "should add the CA Certificate to the store" do Puppet.settings.stubs(:value).with(:cacert).returns "/ca/cert" @store.expects(:add_file).with "/ca/cert" @ca.verify("me") end it "should add the CRL to the store if the crl is enabled" do @store.expects(:add_crl).with "mycrl" @ca.verify("me") end it "should set the store purpose to OpenSSL::X509::PURPOSE_SSL_CLIENT" do Puppet.settings.stubs(:value).with(:cacert).returns "/ca/cert" @store.expects(:add_file).with "/ca/cert" @ca.verify("me") end it "should set the store flags to check the crl" do @store.expects(:flags=).with OpenSSL::X509::V_FLAG_CRL_CHECK_ALL|OpenSSL::X509::V_FLAG_CRL_CHECK @ca.verify("me") end it "should use the store to verify the certificate" do @cert.expects(:content).returns "mycert" @store.expects(:verify).with("mycert").returns true @ca.verify("me") end it "should fail if the verification returns false" do @cert.expects(:content).returns "mycert" @store.expects(:verify).with("mycert").returns false - lambda { @ca.verify("me") }.should raise_error + expect { @ca.verify("me") }.to raise_error end end describe "and revoking certificates" do before do @crl = mock 'crl' @ca.stubs(:crl).returns @crl @ca.stubs(:next_serial).returns 10 @real_cert = stub 'real_cert', :serial => 15 @cert = stub 'cert', :content => @real_cert Puppet::SSL::Certificate.indirection.stubs(:find).returns @cert end it "should fail if the certificate revocation list is disabled" do @ca.stubs(:crl).returns false - lambda { @ca.revoke('ca_testing') }.should raise_error(ArgumentError) + expect { @ca.revoke('ca_testing') }.to raise_error(ArgumentError) end it "should delegate the revocation to its CRL" do @ca.crl.expects(:revoke) @ca.revoke('host') end it "should get the serial number from the local certificate if it exists" do @ca.crl.expects(:revoke).with { |serial, key| serial == 15 } Puppet::SSL::Certificate.indirection.expects(:find).with("host").returns @cert @ca.revoke('host') end it "should get the serial number from inventory if no local certificate exists" do real_cert = stub 'real_cert', :serial => 15 cert = stub 'cert', :content => real_cert Puppet::SSL::Certificate.indirection.expects(:find).with("host").returns nil @ca.inventory.expects(:serial).with("host").returns 16 @ca.crl.expects(:revoke).with { |serial, key| serial == 16 } @ca.revoke('host') end end it "should be able to generate a complete new SSL host" do @ca.should respond_to(:generate) end describe "and generating certificates" do before do @host = stub 'host', :generate_certificate_request => nil Puppet::SSL::Host.stubs(:new).returns @host Puppet::SSL::Certificate.indirection.stubs(:find).returns nil @ca.stubs(:sign) end it "should fail if a certificate already exists for the host" do Puppet::SSL::Certificate.indirection.expects(:find).with("him").returns "something" - lambda { @ca.generate("him") }.should raise_error(ArgumentError) + expect { @ca.generate("him") }.to raise_error(ArgumentError) end it "should create a new Host instance with the correct name" do Puppet::SSL::Host.expects(:new).with("him").returns @host @ca.generate("him") end it "should use the Host to generate the certificate request" do @host.expects :generate_certificate_request @ca.generate("him") end it "should sign the generated request" do @ca.expects(:sign).with("him", false) @ca.generate("him") end end end end diff --git a/spec/unit/type/cron_spec.rb b/spec/unit/type/cron_spec.rb index 7adafff48..b7731dbe1 100755 --- a/spec/unit/type/cron_spec.rb +++ b/spec/unit/type/cron_spec.rb @@ -1,497 +1,497 @@ #!/usr/bin/env rspec require 'spec_helper' describe Puppet::Type.type(:cron), :unless => Puppet.features.microsoft_windows? do before do @provider_class = described_class.provide(:simple) { mk_resource_methods } @provider_class.stubs(:suitable?).returns true described_class.stubs(:defaultprovider).returns @provider_class end it "should have :name be its namevar" do described_class.key_attributes.should == [:name] end describe "when validating attributes" do [:name, :provider].each do |param| it "should have a #{param} parameter" do described_class.attrtype(param).should == :param end end [:command, :special, :minute, :hour, :weekday, :month, :monthday, :environment, :user, :target].each do |property| it "should have a #{property} property" do described_class.attrtype(property).should == :property end end [:command, :minute, :hour, :weekday, :month, :monthday].each do |cronparam| it "should have #{cronparam} of type CronParam" do described_class.attrclass(cronparam).ancestors.should include CronParam end end end describe "when validating values" do describe "ensure" do it "should support present as a value for ensure" do - proc { described_class.new(:name => 'foo', :ensure => :present) }.should_not raise_error + expect { described_class.new(:name => 'foo', :ensure => :present) }.to_not raise_error end it "should support absent as a value for ensure" do - proc { described_class.new(:name => 'foo', :ensure => :present) }.should_not raise_error + expect { described_class.new(:name => 'foo', :ensure => :present) }.to_not raise_error end it "should not support other values" do - proc { described_class.new(:name => 'foo', :ensure => :foo) }.should raise_error(Puppet::Error, /Invalid value/) + expect { described_class.new(:name => 'foo', :ensure => :foo) }.to raise_error(Puppet::Error, /Invalid value/) end end describe "minute" do it "should support absent" do - proc { described_class.new(:name => 'foo', :minute => 'absent') }.should_not raise_error + expect { described_class.new(:name => 'foo', :minute => 'absent') }.to_not raise_error end it "should support *" do - proc { described_class.new(:name => 'foo', :minute => '*') }.should_not raise_error + expect { described_class.new(:name => 'foo', :minute => '*') }.to_not raise_error end it "should translate absent to :absent" do described_class.new(:name => 'foo', :minute => 'absent')[:minute].should == :absent end it "should translate * to :absent" do described_class.new(:name => 'foo', :minute => '*')[:minute].should == :absent end it "should support valid single values" do - proc { described_class.new(:name => 'foo', :minute => '0') }.should_not raise_error - proc { described_class.new(:name => 'foo', :minute => '1') }.should_not raise_error - proc { described_class.new(:name => 'foo', :minute => '59') }.should_not raise_error + expect { described_class.new(:name => 'foo', :minute => '0') }.to_not raise_error + expect { described_class.new(:name => 'foo', :minute => '1') }.to_not raise_error + expect { described_class.new(:name => 'foo', :minute => '59') }.to_not raise_error end it "should not support non numeric characters" do - proc { described_class.new(:name => 'foo', :minute => 'z59') }.should raise_error(Puppet::Error, /z59 is not a valid minute/) - proc { described_class.new(:name => 'foo', :minute => '5z9') }.should raise_error(Puppet::Error, /5z9 is not a valid minute/) - proc { described_class.new(:name => 'foo', :minute => '59z') }.should raise_error(Puppet::Error, /59z is not a valid minute/) + expect { described_class.new(:name => 'foo', :minute => 'z59') }.to raise_error(Puppet::Error, /z59 is not a valid minute/) + expect { described_class.new(:name => 'foo', :minute => '5z9') }.to raise_error(Puppet::Error, /5z9 is not a valid minute/) + expect { described_class.new(:name => 'foo', :minute => '59z') }.to raise_error(Puppet::Error, /59z is not a valid minute/) end it "should not support single values out of range" do - proc { described_class.new(:name => 'foo', :minute => '-1') }.should raise_error(Puppet::Error, /-1 is not a valid minute/) - proc { described_class.new(:name => 'foo', :minute => '60') }.should raise_error(Puppet::Error, /60 is not a valid minute/) - proc { described_class.new(:name => 'foo', :minute => '61') }.should raise_error(Puppet::Error, /61 is not a valid minute/) - proc { described_class.new(:name => 'foo', :minute => '120') }.should raise_error(Puppet::Error, /120 is not a valid minute/) + expect { described_class.new(:name => 'foo', :minute => '-1') }.to raise_error(Puppet::Error, /-1 is not a valid minute/) + expect { described_class.new(:name => 'foo', :minute => '60') }.to raise_error(Puppet::Error, /60 is not a valid minute/) + expect { described_class.new(:name => 'foo', :minute => '61') }.to raise_error(Puppet::Error, /61 is not a valid minute/) + expect { described_class.new(:name => 'foo', :minute => '120') }.to raise_error(Puppet::Error, /120 is not a valid minute/) end it "should support valid multiple values" do - proc { described_class.new(:name => 'foo', :minute => ['0','1','59'] ) }.should_not raise_error - proc { described_class.new(:name => 'foo', :minute => ['40','30','20'] ) }.should_not raise_error - proc { described_class.new(:name => 'foo', :minute => ['10','30','20'] ) }.should_not raise_error + expect { described_class.new(:name => 'foo', :minute => ['0','1','59'] ) }.to_not raise_error + expect { described_class.new(:name => 'foo', :minute => ['40','30','20'] ) }.to_not raise_error + expect { described_class.new(:name => 'foo', :minute => ['10','30','20'] ) }.to_not raise_error end it "should not support multiple values if at least one is invalid" do # one invalid - proc { described_class.new(:name => 'foo', :minute => ['0','1','60'] ) }.should raise_error(Puppet::Error, /60 is not a valid minute/) - proc { described_class.new(:name => 'foo', :minute => ['0','120','59'] ) }.should raise_error(Puppet::Error, /120 is not a valid minute/) - proc { described_class.new(:name => 'foo', :minute => ['-1','1','59'] ) }.should raise_error(Puppet::Error, /-1 is not a valid minute/) + expect { described_class.new(:name => 'foo', :minute => ['0','1','60'] ) }.to raise_error(Puppet::Error, /60 is not a valid minute/) + expect { described_class.new(:name => 'foo', :minute => ['0','120','59'] ) }.to raise_error(Puppet::Error, /120 is not a valid minute/) + expect { described_class.new(:name => 'foo', :minute => ['-1','1','59'] ) }.to raise_error(Puppet::Error, /-1 is not a valid minute/) # two invalid - proc { described_class.new(:name => 'foo', :minute => ['0','61','62'] ) }.should raise_error(Puppet::Error, /(61|62) is not a valid minute/) + expect { described_class.new(:name => 'foo', :minute => ['0','61','62'] ) }.to raise_error(Puppet::Error, /(61|62) is not a valid minute/) # all invalid - proc { described_class.new(:name => 'foo', :minute => ['-1','61','62'] ) }.should raise_error(Puppet::Error, /(-1|61|62) is not a valid minute/) + expect { described_class.new(:name => 'foo', :minute => ['-1','61','62'] ) }.to raise_error(Puppet::Error, /(-1|61|62) is not a valid minute/) end it "should support valid step syntax" do - proc { described_class.new(:name => 'foo', :minute => '*/2' ) }.should_not raise_error - proc { described_class.new(:name => 'foo', :minute => '10-16/2' ) }.should_not raise_error + expect { described_class.new(:name => 'foo', :minute => '*/2' ) }.to_not raise_error + expect { described_class.new(:name => 'foo', :minute => '10-16/2' ) }.to_not raise_error end it "should not support invalid steps" do - proc { described_class.new(:name => 'foo', :minute => '*/A' ) }.should raise_error(Puppet::Error, /\*\/A is not a valid minute/) - proc { described_class.new(:name => 'foo', :minute => '*/2A' ) }.should raise_error(Puppet::Error, /\*\/2A is not a valid minute/) + expect { described_class.new(:name => 'foo', :minute => '*/A' ) }.to raise_error(Puppet::Error, /\*\/A is not a valid minute/) + expect { described_class.new(:name => 'foo', :minute => '*/2A' ) }.to raise_error(Puppet::Error, /\*\/2A is not a valid minute/) # As it turns out cron does not complaining about steps that exceed the valid range - # proc { described_class.new(:name => 'foo', :minute => '*/120' ) }.should raise_error(Puppet::Error, /is not a valid minute/) + # expect { described_class.new(:name => 'foo', :minute => '*/120' ) }.to raise_error(Puppet::Error, /is not a valid minute/) end end describe "hour" do it "should support absent" do - proc { described_class.new(:name => 'foo', :hour => 'absent') }.should_not raise_error + expect { described_class.new(:name => 'foo', :hour => 'absent') }.to_not raise_error end it "should support *" do - proc { described_class.new(:name => 'foo', :hour => '*') }.should_not raise_error + expect { described_class.new(:name => 'foo', :hour => '*') }.to_not raise_error end it "should translate absent to :absent" do described_class.new(:name => 'foo', :hour => 'absent')[:hour].should == :absent end it "should translate * to :absent" do described_class.new(:name => 'foo', :hour => '*')[:hour].should == :absent end it "should support valid single values" do - proc { described_class.new(:name => 'foo', :hour => '0') }.should_not raise_error - proc { described_class.new(:name => 'foo', :hour => '11') }.should_not raise_error - proc { described_class.new(:name => 'foo', :hour => '12') }.should_not raise_error - proc { described_class.new(:name => 'foo', :hour => '13') }.should_not raise_error - proc { described_class.new(:name => 'foo', :hour => '23') }.should_not raise_error + expect { described_class.new(:name => 'foo', :hour => '0') }.to_not raise_error + expect { described_class.new(:name => 'foo', :hour => '11') }.to_not raise_error + expect { described_class.new(:name => 'foo', :hour => '12') }.to_not raise_error + expect { described_class.new(:name => 'foo', :hour => '13') }.to_not raise_error + expect { described_class.new(:name => 'foo', :hour => '23') }.to_not raise_error end it "should not support non numeric characters" do - proc { described_class.new(:name => 'foo', :hour => 'z15') }.should raise_error(Puppet::Error, /z15 is not a valid hour/) - proc { described_class.new(:name => 'foo', :hour => '1z5') }.should raise_error(Puppet::Error, /1z5 is not a valid hour/) - proc { described_class.new(:name => 'foo', :hour => '15z') }.should raise_error(Puppet::Error, /15z is not a valid hour/) + expect { described_class.new(:name => 'foo', :hour => 'z15') }.to raise_error(Puppet::Error, /z15 is not a valid hour/) + expect { described_class.new(:name => 'foo', :hour => '1z5') }.to raise_error(Puppet::Error, /1z5 is not a valid hour/) + expect { described_class.new(:name => 'foo', :hour => '15z') }.to raise_error(Puppet::Error, /15z is not a valid hour/) end it "should not support single values out of range" do - proc { described_class.new(:name => 'foo', :hour => '-1') }.should raise_error(Puppet::Error, /-1 is not a valid hour/) - proc { described_class.new(:name => 'foo', :hour => '24') }.should raise_error(Puppet::Error, /24 is not a valid hour/) - proc { described_class.new(:name => 'foo', :hour => '120') }.should raise_error(Puppet::Error, /120 is not a valid hour/) + expect { described_class.new(:name => 'foo', :hour => '-1') }.to raise_error(Puppet::Error, /-1 is not a valid hour/) + expect { described_class.new(:name => 'foo', :hour => '24') }.to raise_error(Puppet::Error, /24 is not a valid hour/) + expect { described_class.new(:name => 'foo', :hour => '120') }.to raise_error(Puppet::Error, /120 is not a valid hour/) end it "should support valid multiple values" do - proc { described_class.new(:name => 'foo', :hour => ['0','1','23'] ) }.should_not raise_error - proc { described_class.new(:name => 'foo', :hour => ['5','16','14'] ) }.should_not raise_error - proc { described_class.new(:name => 'foo', :hour => ['16','13','9'] ) }.should_not raise_error + expect { described_class.new(:name => 'foo', :hour => ['0','1','23'] ) }.to_not raise_error + expect { described_class.new(:name => 'foo', :hour => ['5','16','14'] ) }.to_not raise_error + expect { described_class.new(:name => 'foo', :hour => ['16','13','9'] ) }.to_not raise_error end it "should not support multiple values if at least one is invalid" do # one invalid - proc { described_class.new(:name => 'foo', :hour => ['0','1','24'] ) }.should raise_error(Puppet::Error, /24 is not a valid hour/) - proc { described_class.new(:name => 'foo', :hour => ['0','-1','5'] ) }.should raise_error(Puppet::Error, /-1 is not a valid hour/) - proc { described_class.new(:name => 'foo', :hour => ['-1','1','23'] ) }.should raise_error(Puppet::Error, /-1 is not a valid hour/) + expect { described_class.new(:name => 'foo', :hour => ['0','1','24'] ) }.to raise_error(Puppet::Error, /24 is not a valid hour/) + expect { described_class.new(:name => 'foo', :hour => ['0','-1','5'] ) }.to raise_error(Puppet::Error, /-1 is not a valid hour/) + expect { described_class.new(:name => 'foo', :hour => ['-1','1','23'] ) }.to raise_error(Puppet::Error, /-1 is not a valid hour/) # two invalid - proc { described_class.new(:name => 'foo', :hour => ['0','25','26'] ) }.should raise_error(Puppet::Error, /(25|26) is not a valid hour/) + expect { described_class.new(:name => 'foo', :hour => ['0','25','26'] ) }.to raise_error(Puppet::Error, /(25|26) is not a valid hour/) # all invalid - proc { described_class.new(:name => 'foo', :hour => ['-1','24','120'] ) }.should raise_error(Puppet::Error, /(-1|24|120) is not a valid hour/) + expect { described_class.new(:name => 'foo', :hour => ['-1','24','120'] ) }.to raise_error(Puppet::Error, /(-1|24|120) is not a valid hour/) end it "should support valid step syntax" do - proc { described_class.new(:name => 'foo', :hour => '*/2' ) }.should_not raise_error - proc { described_class.new(:name => 'foo', :hour => '10-18/4' ) }.should_not raise_error + expect { described_class.new(:name => 'foo', :hour => '*/2' ) }.to_not raise_error + expect { described_class.new(:name => 'foo', :hour => '10-18/4' ) }.to_not raise_error end it "should not support invalid steps" do - proc { described_class.new(:name => 'foo', :hour => '*/A' ) }.should raise_error(Puppet::Error, /\*\/A is not a valid hour/) - proc { described_class.new(:name => 'foo', :hour => '*/2A' ) }.should raise_error(Puppet::Error, /\*\/2A is not a valid hour/) + expect { described_class.new(:name => 'foo', :hour => '*/A' ) }.to raise_error(Puppet::Error, /\*\/A is not a valid hour/) + expect { described_class.new(:name => 'foo', :hour => '*/2A' ) }.to raise_error(Puppet::Error, /\*\/2A is not a valid hour/) # As it turns out cron does not complaining about steps that exceed the valid range - # proc { described_class.new(:name => 'foo', :hour => '*/26' ) }.should raise_error(Puppet::Error, /is not a valid hour/) + # expect { described_class.new(:name => 'foo', :hour => '*/26' ) }.to raise_error(Puppet::Error, /is not a valid hour/) end end describe "weekday" do it "should support absent" do - proc { described_class.new(:name => 'foo', :weekday => 'absent') }.should_not raise_error + expect { described_class.new(:name => 'foo', :weekday => 'absent') }.to_not raise_error end it "should support *" do - proc { described_class.new(:name => 'foo', :weekday => '*') }.should_not raise_error + expect { described_class.new(:name => 'foo', :weekday => '*') }.to_not raise_error end it "should translate absent to :absent" do described_class.new(:name => 'foo', :weekday => 'absent')[:weekday].should == :absent end it "should translate * to :absent" do described_class.new(:name => 'foo', :weekday => '*')[:weekday].should == :absent end it "should support valid numeric weekdays" do - proc { described_class.new(:name => 'foo', :weekday => '0') }.should_not raise_error - proc { described_class.new(:name => 'foo', :weekday => '1') }.should_not raise_error - proc { described_class.new(:name => 'foo', :weekday => '6') }.should_not raise_error + expect { described_class.new(:name => 'foo', :weekday => '0') }.to_not raise_error + expect { described_class.new(:name => 'foo', :weekday => '1') }.to_not raise_error + expect { described_class.new(:name => 'foo', :weekday => '6') }.to_not raise_error # According to http://www.manpagez.com/man/5/crontab 7 is also valid (Sunday) - proc { described_class.new(:name => 'foo', :weekday => '7') }.should_not raise_error + expect { described_class.new(:name => 'foo', :weekday => '7') }.to_not raise_error end it "should support valid weekdays as words (long version)" do - proc { described_class.new(:name => 'foo', :weekday => 'Monday') }.should_not raise_error - proc { described_class.new(:name => 'foo', :weekday => 'Tuesday') }.should_not raise_error - proc { described_class.new(:name => 'foo', :weekday => 'Wednesday') }.should_not raise_error - proc { described_class.new(:name => 'foo', :weekday => 'Thursday') }.should_not raise_error - proc { described_class.new(:name => 'foo', :weekday => 'Friday') }.should_not raise_error - proc { described_class.new(:name => 'foo', :weekday => 'Saturday') }.should_not raise_error - proc { described_class.new(:name => 'foo', :weekday => 'Sunday') }.should_not raise_error + expect { described_class.new(:name => 'foo', :weekday => 'Monday') }.to_not raise_error + expect { described_class.new(:name => 'foo', :weekday => 'Tuesday') }.to_not raise_error + expect { described_class.new(:name => 'foo', :weekday => 'Wednesday') }.to_not raise_error + expect { described_class.new(:name => 'foo', :weekday => 'Thursday') }.to_not raise_error + expect { described_class.new(:name => 'foo', :weekday => 'Friday') }.to_not raise_error + expect { described_class.new(:name => 'foo', :weekday => 'Saturday') }.to_not raise_error + expect { described_class.new(:name => 'foo', :weekday => 'Sunday') }.to_not raise_error end it "should support valid weekdays as words (3 character version)" do - proc { described_class.new(:name => 'foo', :weekday => 'Mon') }.should_not raise_error - proc { described_class.new(:name => 'foo', :weekday => 'Tue') }.should_not raise_error - proc { described_class.new(:name => 'foo', :weekday => 'Wed') }.should_not raise_error - proc { described_class.new(:name => 'foo', :weekday => 'Thu') }.should_not raise_error - proc { described_class.new(:name => 'foo', :weekday => 'Fri') }.should_not raise_error - proc { described_class.new(:name => 'foo', :weekday => 'Sat') }.should_not raise_error - proc { described_class.new(:name => 'foo', :weekday => 'Sun') }.should_not raise_error + expect { described_class.new(:name => 'foo', :weekday => 'Mon') }.to_not raise_error + expect { described_class.new(:name => 'foo', :weekday => 'Tue') }.to_not raise_error + expect { described_class.new(:name => 'foo', :weekday => 'Wed') }.to_not raise_error + expect { described_class.new(:name => 'foo', :weekday => 'Thu') }.to_not raise_error + expect { described_class.new(:name => 'foo', :weekday => 'Fri') }.to_not raise_error + expect { described_class.new(:name => 'foo', :weekday => 'Sat') }.to_not raise_error + expect { described_class.new(:name => 'foo', :weekday => 'Sun') }.to_not raise_error end it "should not support numeric values out of range" do - proc { described_class.new(:name => 'foo', :weekday => '-1') }.should raise_error(Puppet::Error, /-1 is not a valid weekday/) - proc { described_class.new(:name => 'foo', :weekday => '8') }.should raise_error(Puppet::Error, /8 is not a valid weekday/) + expect { described_class.new(:name => 'foo', :weekday => '-1') }.to raise_error(Puppet::Error, /-1 is not a valid weekday/) + expect { described_class.new(:name => 'foo', :weekday => '8') }.to raise_error(Puppet::Error, /8 is not a valid weekday/) end it "should not support invalid weekday names" do - proc { described_class.new(:name => 'foo', :weekday => 'Sar') }.should raise_error(Puppet::Error, /Sar is not a valid weekday/) + expect { described_class.new(:name => 'foo', :weekday => 'Sar') }.to raise_error(Puppet::Error, /Sar is not a valid weekday/) end it "should support valid multiple values" do - proc { described_class.new(:name => 'foo', :weekday => ['0','1','6'] ) }.should_not raise_error - proc { described_class.new(:name => 'foo', :weekday => ['Mon','Wed','Friday'] ) }.should_not raise_error + expect { described_class.new(:name => 'foo', :weekday => ['0','1','6'] ) }.to_not raise_error + expect { described_class.new(:name => 'foo', :weekday => ['Mon','Wed','Friday'] ) }.to_not raise_error end it "should not support multiple values if at least one is invalid" do # one invalid - proc { described_class.new(:name => 'foo', :weekday => ['0','1','8'] ) }.should raise_error(Puppet::Error, /8 is not a valid weekday/) - proc { described_class.new(:name => 'foo', :weekday => ['Mon','Fii','Sat'] ) }.should raise_error(Puppet::Error, /Fii is not a valid weekday/) + expect { described_class.new(:name => 'foo', :weekday => ['0','1','8'] ) }.to raise_error(Puppet::Error, /8 is not a valid weekday/) + expect { described_class.new(:name => 'foo', :weekday => ['Mon','Fii','Sat'] ) }.to raise_error(Puppet::Error, /Fii is not a valid weekday/) # two invalid - proc { described_class.new(:name => 'foo', :weekday => ['Mos','Fii','Sat'] ) }.should raise_error(Puppet::Error, /(Mos|Fii) is not a valid weekday/) + expect { described_class.new(:name => 'foo', :weekday => ['Mos','Fii','Sat'] ) }.to raise_error(Puppet::Error, /(Mos|Fii) is not a valid weekday/) # all invalid - proc { described_class.new(:name => 'foo', :weekday => ['Mos','Fii','Saa'] ) }.should raise_error(Puppet::Error, /(Mos|Fii|Saa) is not a valid weekday/) - proc { described_class.new(:name => 'foo', :weekday => ['-1','8','11'] ) }.should raise_error(Puppet::Error, /(-1|8|11) is not a valid weekday/) + expect { described_class.new(:name => 'foo', :weekday => ['Mos','Fii','Saa'] ) }.to raise_error(Puppet::Error, /(Mos|Fii|Saa) is not a valid weekday/) + expect { described_class.new(:name => 'foo', :weekday => ['-1','8','11'] ) }.to raise_error(Puppet::Error, /(-1|8|11) is not a valid weekday/) end it "should support valid step syntax" do - proc { described_class.new(:name => 'foo', :weekday => '*/2' ) }.should_not raise_error - proc { described_class.new(:name => 'foo', :weekday => '0-4/2' ) }.should_not raise_error + expect { described_class.new(:name => 'foo', :weekday => '*/2' ) }.to_not raise_error + expect { described_class.new(:name => 'foo', :weekday => '0-4/2' ) }.to_not raise_error end it "should not support invalid steps" do - proc { described_class.new(:name => 'foo', :weekday => '*/A' ) }.should raise_error(Puppet::Error, /\*\/A is not a valid weekday/) - proc { described_class.new(:name => 'foo', :weekday => '*/2A' ) }.should raise_error(Puppet::Error, /\*\/2A is not a valid weekday/) + expect { described_class.new(:name => 'foo', :weekday => '*/A' ) }.to raise_error(Puppet::Error, /\*\/A is not a valid weekday/) + expect { described_class.new(:name => 'foo', :weekday => '*/2A' ) }.to raise_error(Puppet::Error, /\*\/2A is not a valid weekday/) # As it turns out cron does not complaining about steps that exceed the valid range - # proc { described_class.new(:name => 'foo', :weekday => '*/9' ) }.should raise_error(Puppet::Error, /is not a valid weekday/) + # expect { described_class.new(:name => 'foo', :weekday => '*/9' ) }.to raise_error(Puppet::Error, /is not a valid weekday/) end end describe "month" do it "should support absent" do - proc { described_class.new(:name => 'foo', :month => 'absent') }.should_not raise_error + expect { described_class.new(:name => 'foo', :month => 'absent') }.to_not raise_error end it "should support *" do - proc { described_class.new(:name => 'foo', :month => '*') }.should_not raise_error + expect { described_class.new(:name => 'foo', :month => '*') }.to_not raise_error end it "should translate absent to :absent" do described_class.new(:name => 'foo', :month => 'absent')[:month].should == :absent end it "should translate * to :absent" do described_class.new(:name => 'foo', :month => '*')[:month].should == :absent end it "should support valid numeric values" do - proc { described_class.new(:name => 'foo', :month => '1') }.should_not raise_error - proc { described_class.new(:name => 'foo', :month => '12') }.should_not raise_error + expect { described_class.new(:name => 'foo', :month => '1') }.to_not raise_error + expect { described_class.new(:name => 'foo', :month => '12') }.to_not raise_error end it "should support valid months as words" do - proc { described_class.new(:name => 'foo', :month => 'January') }.should_not raise_error - proc { described_class.new(:name => 'foo', :month => 'February') }.should_not raise_error - proc { described_class.new(:name => 'foo', :month => 'March') }.should_not raise_error - proc { described_class.new(:name => 'foo', :month => 'April') }.should_not raise_error - proc { described_class.new(:name => 'foo', :month => 'May') }.should_not raise_error - proc { described_class.new(:name => 'foo', :month => 'June') }.should_not raise_error - proc { described_class.new(:name => 'foo', :month => 'July') }.should_not raise_error - proc { described_class.new(:name => 'foo', :month => 'August') }.should_not raise_error - proc { described_class.new(:name => 'foo', :month => 'September') }.should_not raise_error - proc { described_class.new(:name => 'foo', :month => 'October') }.should_not raise_error - proc { described_class.new(:name => 'foo', :month => 'November') }.should_not raise_error - proc { described_class.new(:name => 'foo', :month => 'December') }.should_not raise_error + expect { described_class.new(:name => 'foo', :month => 'January') }.to_not raise_error + expect { described_class.new(:name => 'foo', :month => 'February') }.to_not raise_error + expect { described_class.new(:name => 'foo', :month => 'March') }.to_not raise_error + expect { described_class.new(:name => 'foo', :month => 'April') }.to_not raise_error + expect { described_class.new(:name => 'foo', :month => 'May') }.to_not raise_error + expect { described_class.new(:name => 'foo', :month => 'June') }.to_not raise_error + expect { described_class.new(:name => 'foo', :month => 'July') }.to_not raise_error + expect { described_class.new(:name => 'foo', :month => 'August') }.to_not raise_error + expect { described_class.new(:name => 'foo', :month => 'September') }.to_not raise_error + expect { described_class.new(:name => 'foo', :month => 'October') }.to_not raise_error + expect { described_class.new(:name => 'foo', :month => 'November') }.to_not raise_error + expect { described_class.new(:name => 'foo', :month => 'December') }.to_not raise_error end it "should support valid months as words (3 character short version)" do - proc { described_class.new(:name => 'foo', :month => 'Jan') }.should_not raise_error - proc { described_class.new(:name => 'foo', :month => 'Feb') }.should_not raise_error - proc { described_class.new(:name => 'foo', :month => 'Mar') }.should_not raise_error - proc { described_class.new(:name => 'foo', :month => 'Apr') }.should_not raise_error - proc { described_class.new(:name => 'foo', :month => 'May') }.should_not raise_error - proc { described_class.new(:name => 'foo', :month => 'Jun') }.should_not raise_error - proc { described_class.new(:name => 'foo', :month => 'Jul') }.should_not raise_error - proc { described_class.new(:name => 'foo', :month => 'Aug') }.should_not raise_error - proc { described_class.new(:name => 'foo', :month => 'Sep') }.should_not raise_error - proc { described_class.new(:name => 'foo', :month => 'Oct') }.should_not raise_error - proc { described_class.new(:name => 'foo', :month => 'Nov') }.should_not raise_error - proc { described_class.new(:name => 'foo', :month => 'Dec') }.should_not raise_error + expect { described_class.new(:name => 'foo', :month => 'Jan') }.to_not raise_error + expect { described_class.new(:name => 'foo', :month => 'Feb') }.to_not raise_error + expect { described_class.new(:name => 'foo', :month => 'Mar') }.to_not raise_error + expect { described_class.new(:name => 'foo', :month => 'Apr') }.to_not raise_error + expect { described_class.new(:name => 'foo', :month => 'May') }.to_not raise_error + expect { described_class.new(:name => 'foo', :month => 'Jun') }.to_not raise_error + expect { described_class.new(:name => 'foo', :month => 'Jul') }.to_not raise_error + expect { described_class.new(:name => 'foo', :month => 'Aug') }.to_not raise_error + expect { described_class.new(:name => 'foo', :month => 'Sep') }.to_not raise_error + expect { described_class.new(:name => 'foo', :month => 'Oct') }.to_not raise_error + expect { described_class.new(:name => 'foo', :month => 'Nov') }.to_not raise_error + expect { described_class.new(:name => 'foo', :month => 'Dec') }.to_not raise_error end it "should not support numeric values out of range" do - proc { described_class.new(:name => 'foo', :month => '-1') }.should raise_error(Puppet::Error, /-1 is not a valid month/) - proc { described_class.new(:name => 'foo', :month => '0') }.should raise_error(Puppet::Error, /0 is not a valid month/) - proc { described_class.new(:name => 'foo', :month => '13') }.should raise_error(Puppet::Error, /13 is not a valid month/) + expect { described_class.new(:name => 'foo', :month => '-1') }.to raise_error(Puppet::Error, /-1 is not a valid month/) + expect { described_class.new(:name => 'foo', :month => '0') }.to raise_error(Puppet::Error, /0 is not a valid month/) + expect { described_class.new(:name => 'foo', :month => '13') }.to raise_error(Puppet::Error, /13 is not a valid month/) end it "should not support words that are not valid months" do - proc { described_class.new(:name => 'foo', :month => 'Jal') }.should raise_error(Puppet::Error, /Jal is not a valid month/) + expect { described_class.new(:name => 'foo', :month => 'Jal') }.to raise_error(Puppet::Error, /Jal is not a valid month/) end it "should not support single values out of range" do - proc { described_class.new(:name => 'foo', :month => '-1') }.should raise_error(Puppet::Error, /-1 is not a valid month/) - proc { described_class.new(:name => 'foo', :month => '60') }.should raise_error(Puppet::Error, /60 is not a valid month/) - proc { described_class.new(:name => 'foo', :month => '61') }.should raise_error(Puppet::Error, /61 is not a valid month/) - proc { described_class.new(:name => 'foo', :month => '120') }.should raise_error(Puppet::Error, /120 is not a valid month/) + expect { described_class.new(:name => 'foo', :month => '-1') }.to raise_error(Puppet::Error, /-1 is not a valid month/) + expect { described_class.new(:name => 'foo', :month => '60') }.to raise_error(Puppet::Error, /60 is not a valid month/) + expect { described_class.new(:name => 'foo', :month => '61') }.to raise_error(Puppet::Error, /61 is not a valid month/) + expect { described_class.new(:name => 'foo', :month => '120') }.to raise_error(Puppet::Error, /120 is not a valid month/) end it "should support valid multiple values" do - proc { described_class.new(:name => 'foo', :month => ['1','9','12'] ) }.should_not raise_error - proc { described_class.new(:name => 'foo', :month => ['Jan','March','Jul'] ) }.should_not raise_error + expect { described_class.new(:name => 'foo', :month => ['1','9','12'] ) }.to_not raise_error + expect { described_class.new(:name => 'foo', :month => ['Jan','March','Jul'] ) }.to_not raise_error end it "should not support multiple values if at least one is invalid" do # one invalid - proc { described_class.new(:name => 'foo', :month => ['0','1','12'] ) }.should raise_error(Puppet::Error, /0 is not a valid month/) - proc { described_class.new(:name => 'foo', :month => ['1','13','10'] ) }.should raise_error(Puppet::Error, /13 is not a valid month/) - proc { described_class.new(:name => 'foo', :month => ['Jan','Feb','Jxx'] ) }.should raise_error(Puppet::Error, /Jxx is not a valid month/) + expect { described_class.new(:name => 'foo', :month => ['0','1','12'] ) }.to raise_error(Puppet::Error, /0 is not a valid month/) + expect { described_class.new(:name => 'foo', :month => ['1','13','10'] ) }.to raise_error(Puppet::Error, /13 is not a valid month/) + expect { described_class.new(:name => 'foo', :month => ['Jan','Feb','Jxx'] ) }.to raise_error(Puppet::Error, /Jxx is not a valid month/) # two invalid - proc { described_class.new(:name => 'foo', :month => ['Jan','Fex','Jux'] ) }.should raise_error(Puppet::Error, /(Fex|Jux) is not a valid month/) + expect { described_class.new(:name => 'foo', :month => ['Jan','Fex','Jux'] ) }.to raise_error(Puppet::Error, /(Fex|Jux) is not a valid month/) # all invalid - proc { described_class.new(:name => 'foo', :month => ['-1','0','13'] ) }.should raise_error(Puppet::Error, /(-1|0|13) is not a valid month/) - proc { described_class.new(:name => 'foo', :month => ['Jax','Fex','Aux'] ) }.should raise_error(Puppet::Error, /(Jax|Fex|Aux) is not a valid month/) + expect { described_class.new(:name => 'foo', :month => ['-1','0','13'] ) }.to raise_error(Puppet::Error, /(-1|0|13) is not a valid month/) + expect { described_class.new(:name => 'foo', :month => ['Jax','Fex','Aux'] ) }.to raise_error(Puppet::Error, /(Jax|Fex|Aux) is not a valid month/) end it "should support valid step syntax" do - proc { described_class.new(:name => 'foo', :month => '*/2' ) }.should_not raise_error - proc { described_class.new(:name => 'foo', :month => '1-12/3' ) }.should_not raise_error + expect { described_class.new(:name => 'foo', :month => '*/2' ) }.to_not raise_error + expect { described_class.new(:name => 'foo', :month => '1-12/3' ) }.to_not raise_error end it "should not support invalid steps" do - proc { described_class.new(:name => 'foo', :month => '*/A' ) }.should raise_error(Puppet::Error, /\*\/A is not a valid month/) - proc { described_class.new(:name => 'foo', :month => '*/2A' ) }.should raise_error(Puppet::Error, /\*\/2A is not a valid month/) + expect { described_class.new(:name => 'foo', :month => '*/A' ) }.to raise_error(Puppet::Error, /\*\/A is not a valid month/) + expect { described_class.new(:name => 'foo', :month => '*/2A' ) }.to raise_error(Puppet::Error, /\*\/2A is not a valid month/) # As it turns out cron does not complaining about steps that exceed the valid range - # proc { described_class.new(:name => 'foo', :month => '*/13' ) }.should raise_error(Puppet::Error, /is not a valid month/) + # expect { described_class.new(:name => 'foo', :month => '*/13' ) }.to raise_error(Puppet::Error, /is not a valid month/) end end describe "monthday" do it "should support absent" do - proc { described_class.new(:name => 'foo', :monthday => 'absent') }.should_not raise_error + expect { described_class.new(:name => 'foo', :monthday => 'absent') }.to_not raise_error end it "should support *" do - proc { described_class.new(:name => 'foo', :monthday => '*') }.should_not raise_error + expect { described_class.new(:name => 'foo', :monthday => '*') }.to_not raise_error end it "should translate absent to :absent" do described_class.new(:name => 'foo', :monthday => 'absent')[:monthday].should == :absent end it "should translate * to :absent" do described_class.new(:name => 'foo', :monthday => '*')[:monthday].should == :absent end it "should support valid single values" do - proc { described_class.new(:name => 'foo', :monthday => '1') }.should_not raise_error - proc { described_class.new(:name => 'foo', :monthday => '30') }.should_not raise_error - proc { described_class.new(:name => 'foo', :monthday => '31') }.should_not raise_error + expect { described_class.new(:name => 'foo', :monthday => '1') }.to_not raise_error + expect { described_class.new(:name => 'foo', :monthday => '30') }.to_not raise_error + expect { described_class.new(:name => 'foo', :monthday => '31') }.to_not raise_error end it "should not support non numeric characters" do - proc { described_class.new(:name => 'foo', :monthday => 'z23') }.should raise_error(Puppet::Error, /z23 is not a valid monthday/) - proc { described_class.new(:name => 'foo', :monthday => '2z3') }.should raise_error(Puppet::Error, /2z3 is not a valid monthday/) - proc { described_class.new(:name => 'foo', :monthday => '23z') }.should raise_error(Puppet::Error, /23z is not a valid monthday/) + expect { described_class.new(:name => 'foo', :monthday => 'z23') }.to raise_error(Puppet::Error, /z23 is not a valid monthday/) + expect { described_class.new(:name => 'foo', :monthday => '2z3') }.to raise_error(Puppet::Error, /2z3 is not a valid monthday/) + expect { described_class.new(:name => 'foo', :monthday => '23z') }.to raise_error(Puppet::Error, /23z is not a valid monthday/) end it "should not support single values out of range" do - proc { described_class.new(:name => 'foo', :monthday => '-1') }.should raise_error(Puppet::Error, /-1 is not a valid monthday/) - proc { described_class.new(:name => 'foo', :monthday => '0') }.should raise_error(Puppet::Error, /0 is not a valid monthday/) - proc { described_class.new(:name => 'foo', :monthday => '32') }.should raise_error(Puppet::Error, /32 is not a valid monthday/) + expect { described_class.new(:name => 'foo', :monthday => '-1') }.to raise_error(Puppet::Error, /-1 is not a valid monthday/) + expect { described_class.new(:name => 'foo', :monthday => '0') }.to raise_error(Puppet::Error, /0 is not a valid monthday/) + expect { described_class.new(:name => 'foo', :monthday => '32') }.to raise_error(Puppet::Error, /32 is not a valid monthday/) end it "should support valid multiple values" do - proc { described_class.new(:name => 'foo', :monthday => ['1','23','31'] ) }.should_not raise_error - proc { described_class.new(:name => 'foo', :monthday => ['31','23','1'] ) }.should_not raise_error - proc { described_class.new(:name => 'foo', :monthday => ['1','31','23'] ) }.should_not raise_error + expect { described_class.new(:name => 'foo', :monthday => ['1','23','31'] ) }.to_not raise_error + expect { described_class.new(:name => 'foo', :monthday => ['31','23','1'] ) }.to_not raise_error + expect { described_class.new(:name => 'foo', :monthday => ['1','31','23'] ) }.to_not raise_error end it "should not support multiple values if at least one is invalid" do # one invalid - proc { described_class.new(:name => 'foo', :monthday => ['1','23','32'] ) }.should raise_error(Puppet::Error, /32 is not a valid monthday/) - proc { described_class.new(:name => 'foo', :monthday => ['-1','12','23'] ) }.should raise_error(Puppet::Error, /-1 is not a valid monthday/) - proc { described_class.new(:name => 'foo', :monthday => ['13','32','30'] ) }.should raise_error(Puppet::Error, /32 is not a valid monthday/) + expect { described_class.new(:name => 'foo', :monthday => ['1','23','32'] ) }.to raise_error(Puppet::Error, /32 is not a valid monthday/) + expect { described_class.new(:name => 'foo', :monthday => ['-1','12','23'] ) }.to raise_error(Puppet::Error, /-1 is not a valid monthday/) + expect { described_class.new(:name => 'foo', :monthday => ['13','32','30'] ) }.to raise_error(Puppet::Error, /32 is not a valid monthday/) # two invalid - proc { described_class.new(:name => 'foo', :monthday => ['-1','0','23'] ) }.should raise_error(Puppet::Error, /(-1|0) is not a valid monthday/) + expect { described_class.new(:name => 'foo', :monthday => ['-1','0','23'] ) }.to raise_error(Puppet::Error, /(-1|0) is not a valid monthday/) # all invalid - proc { described_class.new(:name => 'foo', :monthday => ['-1','0','32'] ) }.should raise_error(Puppet::Error, /(-1|0|32) is not a valid monthday/) + expect { described_class.new(:name => 'foo', :monthday => ['-1','0','32'] ) }.to raise_error(Puppet::Error, /(-1|0|32) is not a valid monthday/) end it "should support valid step syntax" do - proc { described_class.new(:name => 'foo', :monthday => '*/2' ) }.should_not raise_error - proc { described_class.new(:name => 'foo', :monthday => '10-16/2' ) }.should_not raise_error + expect { described_class.new(:name => 'foo', :monthday => '*/2' ) }.to_not raise_error + expect { described_class.new(:name => 'foo', :monthday => '10-16/2' ) }.to_not raise_error end it "should not support invalid steps" do - proc { described_class.new(:name => 'foo', :monthday => '*/A' ) }.should raise_error(Puppet::Error, /\*\/A is not a valid monthday/) - proc { described_class.new(:name => 'foo', :monthday => '*/2A' ) }.should raise_error(Puppet::Error, /\*\/2A is not a valid monthday/) + expect { described_class.new(:name => 'foo', :monthday => '*/A' ) }.to raise_error(Puppet::Error, /\*\/A is not a valid monthday/) + expect { described_class.new(:name => 'foo', :monthday => '*/2A' ) }.to raise_error(Puppet::Error, /\*\/2A is not a valid monthday/) # As it turns out cron does not complaining about steps that exceed the valid range - # proc { described_class.new(:name => 'foo', :monthday => '*/32' ) }.should raise_error(Puppet::Error, /is not a valid monthday/) + # expect { described_class.new(:name => 'foo', :monthday => '*/32' ) }.to raise_error(Puppet::Error, /is not a valid monthday/) end end describe "environment" do it "it should accept an :environment that looks like a path" do - lambda do + expect do described_class.new(:name => 'foo',:environment => 'PATH=/bin:/usr/bin:/usr/sbin') - end.should_not raise_error + end.to_not raise_error end it "should not accept environment variables that do not contain '='" do - lambda do + expect do described_class.new(:name => 'foo',:environment => 'INVALID') - end.should raise_error(Puppet::Error, /Invalid environment setting "INVALID"/) + end.to raise_error(Puppet::Error, /Invalid environment setting "INVALID"/) end it "should accept empty environment variables that do not contain '='" do - lambda do + expect do described_class.new(:name => 'foo',:environment => 'MAILTO=') - end.should_not raise_error(Puppet::Error) + end.to_not raise_error(Puppet::Error) end it "should accept 'absent'" do - lambda do + expect do described_class.new(:name => 'foo',:environment => 'absent') - end.should_not raise_error(Puppet::Error) + end.to_not raise_error(Puppet::Error) end end end describe "when autorequiring resources" do before :each do @user_bob = Puppet::Type.type(:user).new(:name => 'bob', :ensure => :present) @user_alice = Puppet::Type.type(:user).new(:name => 'alice', :ensure => :present) @catalog = Puppet::Resource::Catalog.new @catalog.add_resource @user_bob, @user_alice end it "should autorequire the user" do @resource = described_class.new(:name => 'dummy', :command => '/usr/bin/uptime', :user => 'alice') @catalog.add_resource @resource req = @resource.autorequire req.size.should == 1 req[0].target.must == @resource req[0].source.must == @user_alice end end it "should require a command when adding an entry" do entry = described_class.new(:name => "test_entry", :ensure => :present) - expect { entry.value(:command) }.should raise_error(Puppet::Error, /No command/) + expect { entry.value(:command) }.to raise_error(Puppet::Error, /No command/) end it "should not require a command when removing an entry" do entry = described_class.new(:name => "test_entry", :ensure => :absent) entry.value(:command).should == nil end it "should default to user => root if Etc.getpwuid(Process.uid) returns nil (#12357)" do Etc.expects(:getpwuid).returns(nil) entry = described_class.new(:name => "test_entry", :ensure => :present) entry.value(:user).should eql "root" end end diff --git a/spec/unit/type/exec_spec.rb b/spec/unit/type/exec_spec.rb index 3bb607b4b..6f7447978 100755 --- a/spec/unit/type/exec_spec.rb +++ b/spec/unit/type/exec_spec.rb @@ -1,712 +1,714 @@ #!/usr/bin/env rspec require 'spec_helper' describe Puppet::Type.type(:exec) do include PuppetSpec::Files def exec_tester(command, exitstatus = 0, rest = {}) Puppet.features.stubs(:root?).returns(true) output = rest.delete(:output) || '' tries = rest[:tries] || 1 args = { :name => command, :path => @example_path, :logoutput => false, :loglevel => :err, :returns => 0 }.merge(rest) exec = Puppet::Type.type(:exec).new(args) status = stub "process", :exitstatus => exitstatus Puppet::Util::SUIDManager.expects(:run_and_capture).times(tries). with(command, nil, nil).returns([output, status]) return exec end before do @command = make_absolute('/bin/true whatever') @executable = make_absolute('/bin/true') @bogus_cmd = make_absolute('/bogus/cmd') end describe "when not stubbing the provider" do before do path = tmpdir('path') true_cmd = File.join(path, 'true') false_cmd = File.join(path, 'false') FileUtils.touch(true_cmd) FileUtils.touch(false_cmd) File.chmod(0755, true_cmd) File.chmod(0755, false_cmd) @example_path = [path] end it "should return :executed_command as its event" do resource = Puppet::Type.type(:exec).new :command => @command resource.parameter(:returns).event.name.should == :executed_command end describe "when execing" do it "should use the 'run_and_capture' method to exec" do exec_tester("true").refresh.should == :executed_command end it "should report a failure" do - proc { exec_tester('false', 1).refresh }. - should raise_error(Puppet::Error, /^false returned 1 instead of/) + expect { exec_tester('false', 1).refresh }. + to raise_error(Puppet::Error, /^false returned 1 instead of/) end it "should not report a failure if the exit status is specified in a returns array" do - proc { exec_tester("false", 1, :returns => [0, 1]).refresh }.should_not raise_error + expect { exec_tester("false", 1, :returns => [0, 1]).refresh }.to_not raise_error end it "should report a failure if the exit status is not specified in a returns array" do - proc { exec_tester('false', 1, :returns => [0, 100]).refresh }. - should raise_error(Puppet::Error, /^false returned 1 instead of/) + expect { exec_tester('false', 1, :returns => [0, 100]).refresh }. + to raise_error(Puppet::Error, /^false returned 1 instead of/) end it "should log the output on success" do output = "output1\noutput2\n" exec_tester('false', 0, :output => output, :logoutput => true).refresh output.split("\n").each do |line| log = @logs.shift log.level.should == :err log.message.should == line end end it "should log the output on failure" do output = "output1\noutput2\n" - proc { exec_tester('false', 1, :output => output, :logoutput => true).refresh }. - should raise_error(Puppet::Error) + expect { exec_tester('false', 1, :output => output, :logoutput => true).refresh }. + to raise_error(Puppet::Error) output.split("\n").each do |line| log = @logs.shift log.level.should == :err log.message.should == line end end end describe "when logoutput=>on_failure is set" do it "should log the output on failure" do output = "output1\noutput2\n" - proc { exec_tester('false', 1, :output => output, :logoutput => :on_failure).refresh }. - should raise_error(Puppet::Error, /^false returned 1 instead of/) + expect { exec_tester('false', 1, :output => output, :logoutput => :on_failure).refresh }. + to raise_error(Puppet::Error, /^false returned 1 instead of/) output.split("\n").each do |line| log = @logs.shift log.level.should == :err log.message.should == line end end it "should log the output on failure when returns is specified as an array" do output = "output1\noutput2\n" - proc { + expect { exec_tester('false', 1, :output => output, :returns => [0, 100], :logoutput => :on_failure).refresh - }.should raise_error(Puppet::Error, /^false returned 1 instead of/) + }.to raise_error(Puppet::Error, /^false returned 1 instead of/) output.split("\n").each do |line| log = @logs.shift log.level.should == :err log.message.should == line end end it "shouldn't log the output on success" do exec_tester('true', 0, :output => "a\nb\nc\n", :logoutput => :on_failure).refresh @logs.should == [] end end it "shouldn't log the output on success when non-zero exit status is in a returns array" do exec_tester("true", 100, :output => "a\n", :logoutput => :on_failure, :returns => [1, 100]).refresh @logs.should == [] end describe " when multiple tries are set," do it "should repeat the command attempt 'tries' times on failure and produce an error" do tries = 5 resource = exec_tester("false", 1, :tries => tries, :try_sleep => 0) - proc { resource.refresh }.should raise_error(Puppet::Error) + expect { resource.refresh }.to raise_error(Puppet::Error) end end end it "should be able to autorequire files mentioned in the command" do foo = make_absolute('/bin/foo') catalog = Puppet::Resource::Catalog.new tmp = Puppet::Type.type(:file).new(:name => foo) catalog.add_resource tmp execer = Puppet::Type.type(:exec).new(:name => foo) catalog.add_resource execer catalog.relationship_graph.dependencies(execer).should == [tmp] end describe "when handling the path parameter" do expect = %w{one two three four} { "an array" => expect, "a path-separator delimited list" => expect.join(File::PATH_SEPARATOR), "both array and path-separator delimited lists" => ["one", "two#{File::PATH_SEPARATOR}three", "four"], }.each do |test, input| it "should accept #{test}" do type = Puppet::Type.type(:exec).new(:name => @command, :path => input) type[:path].should == expect end end describe "on platforms where path separator is not :" do before :each do @old_verbosity = $VERBOSE $VERBOSE = nil @old_separator = File::PATH_SEPARATOR File::PATH_SEPARATOR = 'q' end after :each do File::PATH_SEPARATOR = @old_separator $VERBOSE = @old_verbosity end it "should use the path separator of the current platform" do type = Puppet::Type.type(:exec).new(:name => @command, :path => "fooqbarqbaz") type[:path].should == %w[foo bar baz] end end end describe "when setting user" do describe "on POSIX systems", :as_platform => :posix do it "should fail if we are not root" do Puppet.features.stubs(:root?).returns(false) - expect { Puppet::Type.type(:exec).new(:name => '/bin/true whatever', :user => 'input') }. - should raise_error Puppet::Error, /Parameter user failed/ + expect { + Puppet::Type.type(:exec).new(:name => '/bin/true whatever', :user => 'input') + }.to raise_error Puppet::Error, /Parameter user failed/ end ['one', 2, 'root', 4294967295, 4294967296].each do |value| it "should accept '#{value}' as user if we are root" do Puppet.features.stubs(:root?).returns(true) type = Puppet::Type.type(:exec).new(:name => '/bin/true whatever', :user => value) type[:user].should == value end end end describe "on Windows systems", :as_platform => :windows do before :each do Puppet.features.stubs(:root?).returns(true) end it "should reject user parameter" do - expect { Puppet::Type.type(:exec).new(:name => 'c:\windows\notepad.exe', :user => 'input') }. - should raise_error Puppet::Error, /Unable to execute commands as other users on Windows/ + expect { + Puppet::Type.type(:exec).new(:name => 'c:\windows\notepad.exe', :user => 'input') + }.to raise_error Puppet::Error, /Unable to execute commands as other users on Windows/ end end end describe "when setting group" do shared_examples_for "exec[:group]" do ['one', 2, 'wheel', 4294967295, 4294967296].each do |value| it "should accept '#{value}' without error or judgement" do type = Puppet::Type.type(:exec).new(:name => @command, :group => value) type[:group].should == value end end end describe "when running as root" do before :each do Puppet.features.stubs(:root?).returns(true) end it_behaves_like "exec[:group]" end describe "when not running as root" do before :each do Puppet.features.stubs(:root?).returns(false) end it_behaves_like "exec[:group]" end end describe "when setting cwd" do it_should_behave_like "all path parameters", :cwd, :array => false do def instance(path) # Specify shell provider so we don't have to care about command validation Puppet::Type.type(:exec).new(:name => @executable, :cwd => path, :provider => :shell) end end end shared_examples_for "all exec command parameters" do |param| { "relative" => "example", "absolute" => "/bin/example" }.sort.each do |name, command| describe "if command is #{name}" do before :each do @param = param end def test(command, valid) if @param == :name then instance = Puppet::Type.type(:exec).new() else instance = Puppet::Type.type(:exec).new(:name => @executable) end if valid then instance.provider.expects(:validatecmd).returns(true) else instance.provider.expects(:validatecmd).raises(Puppet::Error, "from a stub") end instance[@param] = command end it "should work if the provider calls the command valid" do - expect { test(command, true) }.should_not raise_error + expect { test(command, true) }.to_not raise_error end it "should fail if the provider calls the command invalid" do expect { test(command, false) }. - should raise_error Puppet::Error, /Parameter #{@param} failed: from a stub/ + to raise_error Puppet::Error, /Parameter #{@param} failed: from a stub/ end end end end shared_examples_for "all exec command parameters that take arrays" do |param| describe "when given an array of inputs" do before :each do @test = Puppet::Type.type(:exec).new(:name => @executable) end it "should accept the array when all commands return valid" do input = %w{one two three} @test.provider.expects(:validatecmd).times(input.length).returns(true) @test[param] = input @test[param].should == input end it "should reject the array when any commands return invalid" do input = %w{one two three} @test.provider.expects(:validatecmd).with(input.first).returns(false) input[1..-1].each do |cmd| @test.provider.expects(:validatecmd).with(cmd).returns(true) end @test[param] = input @test[param].should == input end it "should reject the array when all commands return invalid" do input = %w{one two three} @test.provider.expects(:validatecmd).times(input.length).returns(false) @test[param] = input @test[param].should == input end end end describe "when setting refresh" do it_should_behave_like "all exec command parameters", :refresh end describe "for simple parameters" do before :each do @exec = Puppet::Type.type(:exec).new(:name => @executable) end describe "when setting environment" do { "single values" => "foo=bar", "multiple values" => ["foo=bar", "baz=quux"], }.each do |name, data| it "should accept #{name}" do @exec[:environment] = data @exec[:environment].should == data end end { "single values" => "foo", "only values" => ["foo", "bar"], "any values" => ["foo=bar", "baz"] }.each do |name, data| it "should reject #{name} without assignment" do expect { @exec[:environment] = data }. - should raise_error Puppet::Error, /Invalid environment setting/ + to raise_error Puppet::Error, /Invalid environment setting/ end end end describe "when setting timeout" do [0, 0.1, 1, 10, 4294967295].each do |valid| it "should accept '#{valid}' as valid" do @exec[:timeout] = valid @exec[:timeout].should == valid end it "should accept '#{valid}' in an array as valid" do @exec[:timeout] = [valid] @exec[:timeout].should == valid end end ['1/2', '', 'foo', '5foo'].each do |invalid| it "should reject '#{invalid}' as invalid" do expect { @exec[:timeout] = invalid }. - should raise_error Puppet::Error, /The timeout must be a number/ + to raise_error Puppet::Error, /The timeout must be a number/ end it "should reject '#{invalid}' in an array as invalid" do expect { @exec[:timeout] = [invalid] }. - should raise_error Puppet::Error, /The timeout must be a number/ + to raise_error Puppet::Error, /The timeout must be a number/ end end it "should fail if timeout is exceeded" do Puppet::Util.stubs(:execute).with do |cmd,args| sleep 1 true end FileTest.stubs(:file?).returns(false) FileTest.stubs(:file?).with(File.expand_path('/bin/sleep')).returns(true) FileTest.stubs(:executable?).returns(false) FileTest.stubs(:executable?).with(File.expand_path('/bin/sleep')).returns(true) sleep_exec = Puppet::Type.type(:exec).new(:name => 'sleep 1', :path => [File.expand_path('/bin')], :timeout => '0.2') - lambda { sleep_exec.refresh }.should raise_error Puppet::Error, "Command exceeded timeout" + expect { sleep_exec.refresh }.to raise_error Puppet::Error, "Command exceeded timeout" end it "should convert timeout to a float" do command = make_absolute('/bin/false') resource = Puppet::Type.type(:exec).new :command => command, :timeout => "12" resource[:timeout].should be_a(Float) resource[:timeout].should == 12.0 end it "should munge negative timeouts to 0.0" do command = make_absolute('/bin/false') resource = Puppet::Type.type(:exec).new :command => command, :timeout => "-12.0" resource.parameter(:timeout).value.should be_a(Float) resource.parameter(:timeout).value.should == 0.0 end end describe "when setting tries" do [1, 10, 4294967295].each do |valid| it "should accept '#{valid}' as valid" do @exec[:tries] = valid @exec[:tries].should == valid end if "REVISIT: too much test log spam" == "a good thing" then it "should accept '#{valid}' in an array as valid" do pending "inconsistent, but this is not supporting arrays, unlike timeout" @exec[:tries] = [valid] @exec[:tries].should == valid end end end [-3.5, -1, 0, 0.2, '1/2', '1_000_000', '+12', '', 'foo'].each do |invalid| it "should reject '#{invalid}' as invalid" do expect { @exec[:tries] = invalid }. - should raise_error Puppet::Error, /Tries must be an integer/ + to raise_error Puppet::Error, /Tries must be an integer/ end if "REVISIT: too much test log spam" == "a good thing" then it "should reject '#{invalid}' in an array as invalid" do pending "inconsistent, but this is not supporting arrays, unlike timeout" expect { @exec[:tries] = [invalid] }. - should raise_error Puppet::Error, /Tries must be an integer/ + to raise_error Puppet::Error, /Tries must be an integer/ end end end end describe "when setting try_sleep" do [0, 0.2, 1, 10, 4294967295].each do |valid| it "should accept '#{valid}' as valid" do @exec[:try_sleep] = valid @exec[:try_sleep].should == valid end if "REVISIT: too much test log spam" == "a good thing" then it "should accept '#{valid}' in an array as valid" do pending "inconsistent, but this is not supporting arrays, unlike timeout" @exec[:try_sleep] = [valid] @exec[:try_sleep].should == valid end end end { -3.5 => "cannot be a negative number", -1 => "cannot be a negative number", '1/2' => 'must be a number', '1_000_000' => 'must be a number', '+12' => 'must be a number', '' => 'must be a number', 'foo' => 'must be a number', }.each do |invalid, error| it "should reject '#{invalid}' as invalid" do expect { @exec[:try_sleep] = invalid }. - should raise_error Puppet::Error, /try_sleep #{error}/ + to raise_error Puppet::Error, /try_sleep #{error}/ end if "REVISIT: too much test log spam" == "a good thing" then it "should reject '#{invalid}' in an array as invalid" do pending "inconsistent, but this is not supporting arrays, unlike timeout" expect { @exec[:try_sleep] = [invalid] }. - should raise_error Puppet::Error, /try_sleep #{error}/ + to raise_error Puppet::Error, /try_sleep #{error}/ end end end end describe "when setting refreshonly" do [:true, :false].each do |value| it "should accept '#{value}'" do @exec[:refreshonly] = value @exec[:refreshonly].should == value end end [1, 0, "1", "0", "yes", "y", "no", "n"].each do |value| it "should reject '#{value}'" do expect { @exec[:refreshonly] = value }. - should raise_error(Puppet::Error, + to raise_error(Puppet::Error, /Invalid value #{value.inspect}\. Valid values are true, false/ ) end end end end describe "when setting creates" do it_should_behave_like "all path parameters", :creates, :array => true do def instance(path) # Specify shell provider so we don't have to care about command validation Puppet::Type.type(:exec).new(:name => @executable, :creates => path, :provider => :shell) end end end describe "when setting unless" do it_should_behave_like "all exec command parameters", :unless it_should_behave_like "all exec command parameters that take arrays", :unless end describe "when setting onlyif" do it_should_behave_like "all exec command parameters", :onlyif it_should_behave_like "all exec command parameters that take arrays", :onlyif end describe "#check" do before :each do @test = Puppet::Type.type(:exec).new(:name => @executable) end describe ":refreshonly" do { :true => false, :false => true }.each do |input, result| it "should return '#{result}' when given '#{input}'" do @test[:refreshonly] = input @test.check_all_attributes.should == result end end end describe ":creates" do before :each do @exist = tmpfile('exist') FileUtils.touch(@exist) @unexist = tmpfile('unexist') end context "with a single item" do it "should run when the item does not exist" do @test[:creates] = @unexist @test.check_all_attributes.should == true end it "should not run when the item exists" do @test[:creates] = @exist @test.check_all_attributes.should == false end end context "with an array with one item" do it "should run when the item does not exist" do @test[:creates] = [@unexist] @test.check_all_attributes.should == true end it "should not run when the item exists" do @test[:creates] = [@exist] @test.check_all_attributes.should == false end end context "with an array with multiple items" do it "should run when all items do not exist" do @test[:creates] = [@unexist] * 3 @test.check_all_attributes.should == true end it "should not run when one item exists" do @test[:creates] = [@unexist, @exist, @unexist] @test.check_all_attributes.should == false end it "should not run when all items exist" do @test[:creates] = [@exist] * 3 end end end { :onlyif => { :pass => false, :fail => true }, :unless => { :pass => true, :fail => false }, }.each do |param, sense| describe ":#{param}" do before :each do @pass = make_absolute("/magic/pass") @fail = make_absolute("/magic/fail") @pass_status = stub('status', :exitstatus => sense[:pass] ? 0 : 1) @fail_status = stub('status', :exitstatus => sense[:fail] ? 0 : 1) @test.provider.stubs(:checkexe).returns(true) [true, false].each do |check| @test.provider.stubs(:run).with(@pass, check). returns(['test output', @pass_status]) @test.provider.stubs(:run).with(@fail, check). returns(['test output', @fail_status]) end end context "with a single item" do it "should run if the command exits non-zero" do @test[param] = @fail @test.check_all_attributes.should == true end it "should not run if the command exits zero" do @test[param] = @pass @test.check_all_attributes.should == false end end context "with an array with a single item" do it "should run if the command exits non-zero" do @test[param] = [@fail] @test.check_all_attributes.should == true end it "should not run if the command exits zero" do @test[param] = [@pass] @test.check_all_attributes.should == false end end context "with an array with multiple items" do it "should run if all the commands exits non-zero" do @test[param] = [@fail] * 3 @test.check_all_attributes.should == true end it "should not run if one command exits zero" do @test[param] = [@pass, @fail, @pass] @test.check_all_attributes.should == false end it "should not run if all command exits zero" do @test[param] = [@pass] * 3 @test.check_all_attributes.should == false end end it "should emit output to debug" do Puppet::Util::Log.level = :debug @test[param] = @fail @test.check_all_attributes.should == true @logs.shift.message.should == "test output" end end end end describe "#retrieve" do before :each do @exec_resource = Puppet::Type.type(:exec).new(:name => @bogus_cmd) end it "should return :notrun when check_all_attributes returns true" do @exec_resource.stubs(:check_all_attributes).returns true @exec_resource.retrieve[:returns].should == :notrun end it "should return default exit code 0 when check_all_attributes returns false" do @exec_resource.stubs(:check_all_attributes).returns false @exec_resource.retrieve[:returns].should == ['0'] end it "should return the specified exit code when check_all_attributes returns false" do @exec_resource.stubs(:check_all_attributes).returns false @exec_resource[:returns] = 42 @exec_resource.retrieve[:returns].should == ["42"] end end describe "#output" do before :each do @exec_resource = Puppet::Type.type(:exec).new(:name => @bogus_cmd) end it "should return the provider's run output" do provider = stub 'provider' status = stubs "process_status" status.stubs(:exitstatus).returns("0") provider.expects(:run).returns(["silly output", status]) @exec_resource.stubs(:provider).returns(provider) @exec_resource.refresh @exec_resource.output.should == 'silly output' end end describe "#refresh" do before :each do @exec_resource = Puppet::Type.type(:exec).new(:name => @bogus_cmd) end it "should call provider run with the refresh parameter if it is set" do myother_bogus_cmd = make_absolute('/myother/bogus/cmd') provider = stub 'provider' @exec_resource.stubs(:provider).returns(provider) @exec_resource.stubs(:[]).with(:refresh).returns(myother_bogus_cmd) provider.expects(:run).with(myother_bogus_cmd) @exec_resource.refresh end it "should call provider run with the specified command if the refresh parameter is not set" do provider = stub 'provider' status = stubs "process_status" status.stubs(:exitstatus).returns("0") provider.expects(:run).with(@bogus_cmd).returns(["silly output", status]) @exec_resource.stubs(:provider).returns(provider) @exec_resource.refresh end it "should not run the provider if check_all_attributes is false" do @exec_resource.stubs(:check_all_attributes).returns false provider = stub 'provider' provider.expects(:run).never @exec_resource.stubs(:provider).returns(provider) @exec_resource.refresh end end end diff --git a/spec/unit/type/file_spec.rb b/spec/unit/type/file_spec.rb index 63d61f060..b2e4ed2d6 100755 --- a/spec/unit/type/file_spec.rb +++ b/spec/unit/type/file_spec.rb @@ -1,1523 +1,1532 @@ #!/usr/bin/env rspec require 'spec_helper' describe Puppet::Type.type(:file) do include PuppetSpec::Files let(:path) { tmpfile('file_testing') } let(:file) { described_class.new(:path => path, :catalog => catalog) } let(:provider) { file.provider } let(:catalog) { Puppet::Resource::Catalog.new } before do @real_posix = Puppet.features.posix? Puppet.features.stubs("posix?").returns(true) end describe "the path parameter" do describe "on POSIX systems", :if => Puppet.features.posix? do it "should remove trailing slashes" do file[:path] = "/foo/bar/baz/" file[:path].should == "/foo/bar/baz" end it "should remove double slashes" do file[:path] = "/foo/bar//baz" file[:path].should == "/foo/bar/baz" end it "should remove trailing double slashes" do file[:path] = "/foo/bar/baz//" file[:path].should == "/foo/bar/baz" end it "should leave a single slash alone" do file[:path] = "/" file[:path].should == "/" end it "should accept a double-slash at the start of the path" do expect { file[:path] = "//tmp/xxx" # REVISIT: This should be wrong, later. See the next test. # --daniel 2011-01-31 file[:path].should == '/tmp/xxx' - }.should_not raise_error + }.to_not raise_error end # REVISIT: This is pending, because I don't want to try and audit the # entire codebase to make sure we get this right. POSIX treats two (and # exactly two) '/' characters at the start of the path specially. # # See sections 3.2 and 4.11, which allow DomainOS to be all special like # and still have the POSIX branding and all. --daniel 2011-01-31 it "should preserve the double-slash at the start of the path" end describe "on Windows systems", :if => Puppet.features.microsoft_windows? do it "should remove trailing slashes" do file[:path] = "X:/foo/bar/baz/" file[:path].should == "X:/foo/bar/baz" end it "should remove double slashes" do file[:path] = "X:/foo/bar//baz" file[:path].should == "X:/foo/bar/baz" end it "should remove trailing double slashes" do file[:path] = "X:/foo/bar/baz//" file[:path].should == "X:/foo/bar/baz" end it "should leave a drive letter with a slash alone", :'fails_on_ruby_1.9.2' => true do file[:path] = "X:/" file[:path].should == "X:/" end it "should not accept a drive letter without a slash", :'fails_on_ruby_1.9.2' => true do - lambda { file[:path] = "X:" }.should raise_error(/File paths must be fully qualified/) + expect { file[:path] = "X:" }.to raise_error(/File paths must be fully qualified/) end describe "when using UNC filenames", :if => Puppet.features.microsoft_windows?, :'fails_on_ruby_1.9.2' => true do before :each do pending("UNC file paths not yet supported") end it "should remove trailing slashes" do file[:path] = "//server/foo/bar/baz/" file[:path].should == "//server/foo/bar/baz" end it "should remove double slashes" do file[:path] = "//server/foo/bar//baz" file[:path].should == "//server/foo/bar/baz" end it "should remove trailing double slashes" do file[:path] = "//server/foo/bar/baz//" file[:path].should == "//server/foo/bar/baz" end it "should remove a trailing slash from a sharename" do file[:path] = "//server/foo/" file[:path].should == "//server/foo" end it "should not modify a sharename" do file[:path] = "//server/foo" file[:path].should == "//server/foo" end end end end describe "the backup parameter" do [false, 'false', :false].each do |value| it "should disable backup if the value is #{value.inspect}" do file[:backup] = value file[:backup].should == false end end [true, 'true', '.puppet-bak'].each do |value| it "should use .puppet-bak if the value is #{value.inspect}" do file[:backup] = value file[:backup].should == '.puppet-bak' end end it "should use the provided value if it's any other string" do file[:backup] = "over there" file[:backup].should == "over there" end it "should fail if backup is set to anything else" do expect do file[:backup] = 97 end.to raise_error(Puppet::Error, /Invalid backup type 97/) end end describe "the recurse parameter" do it "should default to recursion being disabled" do file[:recurse].should be_false end [true, "true", 10, "inf", "remote"].each do |value| it "should consider #{value} to enable recursion" do file[:recurse] = value file[:recurse].should be_true end end [false, "false", 0].each do |value| it "should consider #{value} to disable recursion" do file[:recurse] = value file[:recurse].should be_false end end it "should warn if recurse is specified as a number" do file[:recurse] = 3 message = /Setting recursion depth with the recurse parameter is now deprecated, please use recurselimit/ @logs.find { |log| log.level == :warning and log.message =~ message}.should_not be_nil end end describe "the recurselimit parameter" do it "should accept integers" do file[:recurselimit] = 12 file[:recurselimit].should == 12 end it "should munge string numbers to number numbers" do file[:recurselimit] = '12' file[:recurselimit].should == 12 end it "should fail if given a non-number" do expect do file[:recurselimit] = 'twelve' end.to raise_error(Puppet::Error, /Invalid value "twelve"/) end end describe "the replace parameter" do [true, :true, :yes].each do |value| it "should consider #{value} to be true" do file[:replace] = value file[:replace].should == :true end end [false, :false, :no].each do |value| it "should consider #{value} to be false" do file[:replace] = value file[:replace].should == :false end end end describe "#[]" do it "should raise an exception" do expect do described_class['anything'] end.to raise_error("Global resource access is deprecated") end end describe ".instances" do it "should return an empty array" do described_class.instances.should == [] end end describe "#asuser" do before :each do # Mocha won't let me just stub SUIDManager.asuser to yield and return, # but it will do exactly that if we're not root. Puppet.features.stubs(:root?).returns false end it "should return the desired owner if they can write to the parent directory" do file[:owner] = 1001 FileTest.stubs(:writable?).with(File.dirname file[:path]).returns true file.asuser.should == 1001 end it "should return nil if the desired owner can't write to the parent directory" do file[:owner] = 1001 FileTest.stubs(:writable?).with(File.dirname file[:path]).returns false file.asuser.should == nil end it "should return nil if not managing owner" do file.asuser.should == nil end end describe "#bucket" do it "should return nil if backup is off" do file[:backup] = false file.bucket.should == nil end it "should not return a bucket if using a file extension for backup" do file[:backup] = '.backup' file.bucket.should == nil end it "should return the default filebucket if using the 'puppet' filebucket" do file[:backup] = 'puppet' bucket = stub('bucket') file.stubs(:default_bucket).returns bucket file.bucket.should == bucket end it "should fail if using a remote filebucket and no catalog exists" do file.catalog = nil file[:backup] = 'my_bucket' expect { file.bucket }.to raise_error(Puppet::Error, "Can not find filebucket for backups without a catalog") end it "should fail if the specified filebucket isn't in the catalog" do file[:backup] = 'my_bucket' expect { file.bucket }.to raise_error(Puppet::Error, "Could not find filebucket my_bucket specified in backup") end it "should use the specified filebucket if it is in the catalog" do file[:backup] = 'my_bucket' filebucket = Puppet::Type.type(:filebucket).new(:name => 'my_bucket') catalog.add_resource(filebucket) file.bucket.should == filebucket.bucket end end describe "#asuser" do before :each do # Mocha won't let me just stub SUIDManager.asuser to yield and return, # but it will do exactly that if we're not root. Puppet.features.stubs(:root?).returns false end it "should return the desired owner if they can write to the parent directory" do file[:owner] = 1001 FileTest.stubs(:writable?).with(File.dirname file[:path]).returns true file.asuser.should == 1001 end it "should return nil if the desired owner can't write to the parent directory" do file[:owner] = 1001 FileTest.stubs(:writable?).with(File.dirname file[:path]).returns false file.asuser.should == nil end it "should return nil if not managing owner" do file.asuser.should == nil end end describe "#bucket" do it "should return nil if backup is off" do file[:backup] = false file.bucket.should == nil end it "should return nil if using a file extension for backup" do file[:backup] = '.backup' file.bucket.should == nil end it "should return the default filebucket if using the 'puppet' filebucket" do file[:backup] = 'puppet' bucket = stub('bucket') file.stubs(:default_bucket).returns bucket file.bucket.should == bucket end it "should fail if using a remote filebucket and no catalog exists" do file.catalog = nil file[:backup] = 'my_bucket' expect { file.bucket }.to raise_error(Puppet::Error, "Can not find filebucket for backups without a catalog") end it "should fail if the specified filebucket isn't in the catalog" do file[:backup] = 'my_bucket' expect { file.bucket }.to raise_error(Puppet::Error, "Could not find filebucket my_bucket specified in backup") end it "should use the specified filebucket if it is in the catalog" do file[:backup] = 'my_bucket' filebucket = Puppet::Type.type(:filebucket).new(:name => 'my_bucket') catalog.add_resource(filebucket) file.bucket.should == filebucket.bucket end end describe "#exist?" do it "should be considered existent if it can be stat'ed" do file.expects(:stat).returns mock('stat') file.must be_exist end it "should be considered nonexistent if it can not be stat'ed" do file.expects(:stat).returns nil file.must_not be_exist end end describe "#eval_generate" do before do @graph = stub 'graph', :add_edge => nil catalog.stubs(:relationship_graph).returns @graph end it "should recurse if recursion is enabled" do resource = stub('resource', :[] => 'resource') file.expects(:recurse).returns [resource] file[:recurse] = true file.eval_generate.should == [resource] end it "should not recurse if recursion is disabled" do file.expects(:recurse).never file[:recurse] = false file.eval_generate.should == [] end end describe "#ancestors" do it "should return the ancestors of the file, in ascending order" do file = described_class.new(:path => make_absolute("/tmp/foo/bar/baz/qux")) pieces = %W[#{make_absolute('/')} tmp foo bar baz] ancestors = file.ancestors ancestors.should_not be_empty ancestors.reverse.each_with_index do |path,i| path.should == File.join(*pieces[0..i]) end end end describe "#flush" do it "should flush all properties that respond to :flush" do file[:source] = File.expand_path(__FILE__) file.parameter(:source).expects(:flush) file.flush end it "should reset its stat reference" do FileUtils.touch(path) stat1 = file.stat file.stat.should equal(stat1) file.flush file.stat.should_not equal(stat1) end end describe "#initialize" do it "should remove a trailing slash from the title to create the path" do title = File.expand_path("/abc/\n\tdef/") file = described_class.new(:title => title) file[:path].should == title end it "should set a desired 'ensure' value if none is set and 'content' is set" do file = described_class.new(:path => path, :content => "/foo/bar") file[:ensure].should == :file end it "should set a desired 'ensure' value if none is set and 'target' is set" do file = described_class.new(:path => path, :target => File.expand_path(__FILE__)) - file[:ensure].should == :symlink + file[:ensure].should == :link end end describe "#mark_children_for_purging" do it "should set each child's ensure to absent" do paths = %w[foo bar baz] children = paths.inject({}) do |children,child| children.merge child => described_class.new(:path => File.join(path, child), :ensure => :present) end file.mark_children_for_purging(children) children.length.should == 3 children.values.each do |child| child[:ensure].should == :absent end end it "should skip children which have a source" do child = described_class.new(:path => path, :ensure => :present, :source => File.expand_path(__FILE__)) file.mark_children_for_purging('foo' => child) child[:ensure].should == :present end end describe "#newchild" do it "should create a new resource relative to the parent" do child = file.newchild('bar') child.must be_a(described_class) child[:path].should == File.join(file[:path], 'bar') end { :ensure => :present, :recurse => true, :recurselimit => 5, :target => "some_target", :source => File.expand_path("some_source"), }.each do |param, value| it "should omit the #{param} parameter" do # Make a new file, because we have to set the param at initialization # or it wouldn't be copied regardless. file = described_class.new(:path => path, param => value) child = file.newchild('bar') child[param].should_not == value end end it "should copy all of the parent resource's 'should' values that were set at initialization" do parent = described_class.new(:path => path, :owner => 'root', :group => 'wheel') child = parent.newchild("my/path") child[:owner].should == 'root' child[:group].should == 'wheel' end it "should not copy default values to the new child" do child = file.newchild("my/path") child.original_parameters.should_not include(:backup) end it "should not copy values to the child which were set by the source" do source = File.expand_path(__FILE__) file[:source] = source metadata = stub 'metadata', :owner => "root", :group => "root", :mode => 0755, :ftype => "file", :checksum => "{md5}whatever", :source => source file.parameter(:source).stubs(:metadata).returns metadata file.parameter(:source).copy_source_values file.class.expects(:new).with { |params| params[:group].nil? } file.newchild("my/path") end end describe "#purge?" do it "should return false if purge is not set" do file.must_not be_purge end it "should return true if purge is set to true" do file[:purge] = true file.must be_purge end it "should return false if purge is set to false" do file[:purge] = false file.must_not be_purge end end describe "#recurse" do before do file[:recurse] = true @metadata = Puppet::FileServing::Metadata end describe "and a source is set" do it "should pass the already-discovered resources to recurse_remote" do file[:source] = File.expand_path(__FILE__) file.stubs(:recurse_local).returns(:foo => "bar") file.expects(:recurse_remote).with(:foo => "bar").returns [] file.recurse end end describe "and a target is set" do it "should use recurse_link" do file[:target] = File.expand_path(__FILE__) file.stubs(:recurse_local).returns(:foo => "bar") file.expects(:recurse_link).with(:foo => "bar").returns [] file.recurse end end it "should use recurse_local if recurse is not remote" do file.expects(:recurse_local).returns({}) file.recurse end it "should not use recurse_local if recurse is remote" do file[:recurse] = :remote file.expects(:recurse_local).never file.recurse end it "should return the generated resources as an array sorted by file path" do one = stub 'one', :[] => "/one" two = stub 'two', :[] => "/one/two" three = stub 'three', :[] => "/three" file.expects(:recurse_local).returns(:one => one, :two => two, :three => three) file.recurse.should == [one, two, three] end describe "and purging is enabled" do before do file[:purge] = true end it "should mark each file for removal" do local = described_class.new(:path => path, :ensure => :present) file.expects(:recurse_local).returns("local" => local) file.recurse local[:ensure].should == :absent end it "should not remove files that exist in the remote repository" do file[:source] = File.expand_path(__FILE__) file.expects(:recurse_local).returns({}) remote = described_class.new(:path => path, :source => File.expand_path(__FILE__), :ensure => :present) file.expects(:recurse_remote).with { |hash| hash["remote"] = remote } file.recurse remote[:ensure].should_not == :absent end end end describe "#remove_less_specific_files" do it "should remove any nested files that are already in the catalog" do foo = described_class.new :path => File.join(file[:path], 'foo') bar = described_class.new :path => File.join(file[:path], 'bar') baz = described_class.new :path => File.join(file[:path], 'baz') catalog.add_resource(foo) catalog.add_resource(bar) file.remove_less_specific_files([foo, bar, baz]).should == [baz] end end describe "#remove_less_specific_files" do it "should remove any nested files that are already in the catalog" do foo = described_class.new :path => File.join(file[:path], 'foo') bar = described_class.new :path => File.join(file[:path], 'bar') baz = described_class.new :path => File.join(file[:path], 'baz') catalog.add_resource(foo) catalog.add_resource(bar) file.remove_less_specific_files([foo, bar, baz]).should == [baz] end end describe "#recurse?" do it "should be true if recurse is true" do file[:recurse] = true file.must be_recurse end it "should be true if recurse is remote" do file[:recurse] = :remote file.must be_recurse end it "should be false if recurse is false" do file[:recurse] = false file.must_not be_recurse end end describe "#recurse_link" do before do @first = stub 'first', :relative_path => "first", :full_path => "/my/first", :ftype => "directory" @second = stub 'second', :relative_path => "second", :full_path => "/my/second", :ftype => "file" @resource = stub 'file', :[]= => nil end it "should pass its target to the :perform_recursion method" do file[:target] = "mylinks" file.expects(:perform_recursion).with("mylinks").returns [@first] file.stubs(:newchild).returns @resource file.recurse_link({}) end it "should ignore the recursively-found '.' file and configure the top-level file to create a directory" do @first.stubs(:relative_path).returns "." file[:target] = "mylinks" file.expects(:perform_recursion).with("mylinks").returns [@first] file.stubs(:newchild).never file.expects(:[]=).with(:ensure, :directory) file.recurse_link({}) end it "should create a new child resource for each generated metadata instance's relative path that doesn't already exist in the children hash" do file.expects(:perform_recursion).returns [@first, @second] file.expects(:newchild).with(@first.relative_path).returns @resource file.recurse_link("second" => @resource) end it "should not create a new child resource for paths that already exist in the children hash" do file.expects(:perform_recursion).returns [@first] file.expects(:newchild).never file.recurse_link("first" => @resource) end it "should set the target to the full path of discovered file and set :ensure to :link if the file is not a directory" do file.stubs(:perform_recursion).returns [@first, @second] file.recurse_link("first" => @resource, "second" => file) file[:ensure].should == :link file[:target].should == "/my/second" end it "should :ensure to :directory if the file is a directory" do file.stubs(:perform_recursion).returns [@first, @second] file.recurse_link("first" => file, "second" => @resource) file[:ensure].should == :directory end it "should return a hash with both created and existing resources with the relative paths as the hash keys" do file.expects(:perform_recursion).returns [@first, @second] file.stubs(:newchild).returns file file.recurse_link("second" => @resource).should == {"second" => @resource, "first" => file} end end describe "#recurse_local" do before do @metadata = stub 'metadata', :relative_path => "my/file" end it "should pass its path to the :perform_recursion method" do file.expects(:perform_recursion).with(file[:path]).returns [@metadata] file.stubs(:newchild) file.recurse_local end it "should return an empty hash if the recursion returns nothing" do file.expects(:perform_recursion).returns nil file.recurse_local.should == {} end it "should create a new child resource with each generated metadata instance's relative path" do file.expects(:perform_recursion).returns [@metadata] file.expects(:newchild).with(@metadata.relative_path).returns "fiebar" file.recurse_local end it "should not create a new child resource for the '.' directory" do @metadata.stubs(:relative_path).returns "." file.expects(:perform_recursion).returns [@metadata] file.expects(:newchild).never file.recurse_local end it "should return a hash of the created resources with the relative paths as the hash keys" do file.expects(:perform_recursion).returns [@metadata] file.expects(:newchild).with("my/file").returns "fiebar" file.recurse_local.should == {"my/file" => "fiebar"} end it "should set checksum_type to none if this file checksum is none" do file[:checksum] = :none Puppet::FileServing::Metadata.indirection.expects(:search).with { |path,params| params[:checksum_type] == :none }.returns [@metadata] file.expects(:newchild).with("my/file").returns "fiebar" file.recurse_local end end describe "#recurse_remote" do let(:my) { File.expand_path('/my') } before do file[:source] = "puppet://foo/bar" @first = Puppet::FileServing::Metadata.new(my, :relative_path => "first") @second = Puppet::FileServing::Metadata.new(my, :relative_path => "second") @first.stubs(:ftype).returns "directory" @second.stubs(:ftype).returns "directory" @parameter = stub 'property', :metadata= => nil @resource = stub 'file', :[]= => nil, :parameter => @parameter end it "should pass its source to the :perform_recursion method" do data = Puppet::FileServing::Metadata.new(File.expand_path("/whatever"), :relative_path => "foobar") file.expects(:perform_recursion).with("puppet://foo/bar").returns [data] file.stubs(:newchild).returns @resource file.recurse_remote({}) end it "should not recurse when the remote file is not a directory" do data = Puppet::FileServing::Metadata.new(File.expand_path("/whatever"), :relative_path => ".") data.stubs(:ftype).returns "file" file.expects(:perform_recursion).with("puppet://foo/bar").returns [data] file.expects(:newchild).never file.recurse_remote({}) end it "should set the source of each returned file to the searched-for URI plus the found relative path" do @first.expects(:source=).with File.join("puppet://foo/bar", @first.relative_path) file.expects(:perform_recursion).returns [@first] file.stubs(:newchild).returns @resource file.recurse_remote({}) end it "should create a new resource for any relative file paths that do not already have a resource" do file.stubs(:perform_recursion).returns [@first] file.expects(:newchild).with("first").returns @resource file.recurse_remote({}).should == {"first" => @resource} end it "should not create a new resource for any relative file paths that do already have a resource" do file.stubs(:perform_recursion).returns [@first] file.expects(:newchild).never file.recurse_remote("first" => @resource) end it "should set the source of each resource to the source of the metadata" do file.stubs(:perform_recursion).returns [@first] @resource.stubs(:[]=) @resource.expects(:[]=).with(:source, File.join("puppet://foo/bar", @first.relative_path)) file.recurse_remote("first" => @resource) end # LAK:FIXME This is a bug, but I can't think of a fix for it. Fortunately it's already # filed, and when it's fixed, we'll just fix the whole flow. it "should set the checksum type to :md5 if the remote file is a file" do @first.stubs(:ftype).returns "file" file.stubs(:perform_recursion).returns [@first] @resource.stubs(:[]=) @resource.expects(:[]=).with(:checksum, :md5) file.recurse_remote("first" => @resource) end it "should store the metadata in the source property for each resource so the source does not have to requery the metadata" do file.stubs(:perform_recursion).returns [@first] @resource.expects(:parameter).with(:source).returns @parameter @parameter.expects(:metadata=).with(@first) file.recurse_remote("first" => @resource) end it "should not create a new resource for the '.' file" do @first.stubs(:relative_path).returns "." file.stubs(:perform_recursion).returns [@first] file.expects(:newchild).never file.recurse_remote({}) end it "should store the metadata in the main file's source property if the relative path is '.'" do @first.stubs(:relative_path).returns "." file.stubs(:perform_recursion).returns [@first] file.parameter(:source).expects(:metadata=).with @first file.recurse_remote("first" => @resource) end describe "and multiple sources are provided" do let(:sources) do h = {} %w{/a /b /c /d}.each do |key| h[key] = URI.unescape(Puppet::Util.path_to_uri(File.expand_path(key)).to_s) end h end describe "and :sourceselect is set to :first" do it "should create file instances for the results for the first source to return any values" do data = Puppet::FileServing::Metadata.new(File.expand_path("/whatever"), :relative_path => "foobar") file[:source] = sources.keys.sort.map { |key| File.expand_path(key) } file.expects(:perform_recursion).with(sources['/a']).returns nil file.expects(:perform_recursion).with(sources['/b']).returns [] file.expects(:perform_recursion).with(sources['/c']).returns [data] file.expects(:perform_recursion).with(sources['/d']).never file.expects(:newchild).with("foobar").returns @resource file.recurse_remote({}) end end describe "and :sourceselect is set to :all" do before do file[:sourceselect] = :all end it "should return every found file that is not in a previous source" do klass = Puppet::FileServing::Metadata file[:source] = abs_path = %w{/a /b /c /d}.map {|f| File.expand_path(f) } file.stubs(:newchild).returns @resource one = [klass.new(abs_path[0], :relative_path => "a")] file.expects(:perform_recursion).with(sources['/a']).returns one file.expects(:newchild).with("a").returns @resource two = [klass.new(abs_path[1], :relative_path => "a"), klass.new(abs_path[1], :relative_path => "b")] file.expects(:perform_recursion).with(sources['/b']).returns two file.expects(:newchild).with("b").returns @resource three = [klass.new(abs_path[2], :relative_path => "a"), klass.new(abs_path[2], :relative_path => "c")] file.expects(:perform_recursion).with(sources['/c']).returns three file.expects(:newchild).with("c").returns @resource file.expects(:perform_recursion).with(sources['/d']).returns [] file.recurse_remote({}) end end end end describe "#perform_recursion" do it "should use Metadata to do its recursion" do Puppet::FileServing::Metadata.indirection.expects(:search) file.perform_recursion(file[:path]) end it "should use the provided path as the key to the search" do Puppet::FileServing::Metadata.indirection.expects(:search).with { |key, options| key == "/foo" } file.perform_recursion("/foo") end it "should return the results of the metadata search" do Puppet::FileServing::Metadata.indirection.expects(:search).returns "foobar" file.perform_recursion(file[:path]).should == "foobar" end it "should pass its recursion value to the search" do file[:recurse] = true Puppet::FileServing::Metadata.indirection.expects(:search).with { |key, options| options[:recurse] == true } file.perform_recursion(file[:path]) end it "should pass true if recursion is remote" do file[:recurse] = :remote Puppet::FileServing::Metadata.indirection.expects(:search).with { |key, options| options[:recurse] == true } file.perform_recursion(file[:path]) end it "should pass its recursion limit value to the search" do file[:recurselimit] = 10 Puppet::FileServing::Metadata.indirection.expects(:search).with { |key, options| options[:recurselimit] == 10 } file.perform_recursion(file[:path]) end it "should configure the search to ignore or manage links" do file[:links] = :manage Puppet::FileServing::Metadata.indirection.expects(:search).with { |key, options| options[:links] == :manage } file.perform_recursion(file[:path]) end it "should pass its 'ignore' setting to the search if it has one" do file[:ignore] = %w{.svn CVS} Puppet::FileServing::Metadata.indirection.expects(:search).with { |key, options| options[:ignore] == %w{.svn CVS} } file.perform_recursion(file[:path]) end end describe "#remove_existing" do it "should do nothing if the file doesn't exist" do file.remove_existing(:file).should == nil end it "should fail if it can't backup the file" do file.stubs(:stat).returns stub('stat') file.stubs(:perform_backup).returns false expect { file.remove_existing(:file) }.to raise_error(Puppet::Error, /Could not back up; will not replace/) end it "should not do anything if the file is already the right type and not a link" do file.stubs(:stat).returns stub('stat', :ftype => 'file') file.remove_existing(:file).should == nil end it "should not remove directories and should not invalidate the stat unless force is set" do # Actually call stat to set @needs_stat to nil file.stat file.stubs(:stat).returns stub('stat', :ftype => 'directory') file.remove_existing(:file) file.instance_variable_get(:@stat).should == nil @logs.should be_any {|log| log.level == :notice and log.message =~ /Not removing directory; use 'force' to override/} end it "should remove a directory if force is set" do file[:force] = true file.stubs(:stat).returns stub('stat', :ftype => 'directory') FileUtils.expects(:rmtree).with(file[:path]) file.remove_existing(:file).should == true end it "should remove an existing file" do file.stubs(:perform_backup).returns true FileUtils.touch(path) file.remove_existing(:directory).should == true File.exists?(file[:path]).should == false end it "should remove an existing link", :unless => Puppet.features.microsoft_windows? do file.stubs(:perform_backup).returns true target = tmpfile('link_target') FileUtils.touch(target) FileUtils.symlink(target, path) file[:target] = target file.remove_existing(:directory).should == true File.exists?(file[:path]).should == false end it "should fail if the file is not a file, link, or directory" do file.stubs(:stat).returns stub('stat', :ftype => 'socket') expect { file.remove_existing(:file) }.to raise_error(Puppet::Error, /Could not back up files of type socket/) end it "should invalidate the existing stat of the file" do # Actually call stat to set @needs_stat to nil file.stat file.stubs(:stat).returns stub('stat', :ftype => 'file') File.stubs(:unlink) file.remove_existing(:directory).should == true file.instance_variable_get(:@stat).should == :needs_stat end end describe "#retrieve" do it "should copy the source values if the 'source' parameter is set" do file[:source] = File.expand_path('/foo/bar') file.parameter(:source).expects(:copy_source_values) file.retrieve end end describe "#should_be_file?" do it "should have a method for determining if the file should be a normal file" do file.must respond_to(:should_be_file?) end it "should be a file if :ensure is set to :file" do file[:ensure] = :file file.must be_should_be_file end it "should be a file if :ensure is set to :present and the file exists as a normal file" do file.stubs(:stat).returns(mock('stat', :ftype => "file")) file[:ensure] = :present file.must be_should_be_file end it "should not be a file if :ensure is set to something other than :file" do file[:ensure] = :directory file.must_not be_should_be_file end it "should not be a file if :ensure is set to :present and the file exists but is not a normal file" do file.stubs(:stat).returns(mock('stat', :ftype => "directory")) file[:ensure] = :present file.must_not be_should_be_file end it "should be a file if :ensure is not set and :content is" do file[:content] = "foo" file.must be_should_be_file end it "should be a file if neither :ensure nor :content is set but the file exists as a normal file" do file.stubs(:stat).returns(mock("stat", :ftype => "file")) file.must be_should_be_file end it "should not be a file if neither :ensure nor :content is set but the file exists but not as a normal file" do file.stubs(:stat).returns(mock("stat", :ftype => "directory")) file.must_not be_should_be_file end end describe "#stat", :unless => Puppet.features.microsoft_windows? do before do target = tmpfile('link_target') FileUtils.touch(target) FileUtils.symlink(target, path) file[:target] = target file[:links] = :manage # so we always use :lstat end it "should stat the target if it is following links" do file[:links] = :follow file.stat.ftype.should == 'file' end it "should stat the link if is it not following links" do file[:links] = :manage file.stat.ftype.should == 'link' end it "should return nil if the file does not exist" do file[:path] = '/foo/bar/baz/non-existent' file.stat.should be_nil end it "should return nil if the file cannot be stat'ed" do dir = tmpfile('link_test_dir') child = File.join(dir, 'some_file') Dir.mkdir(dir) File.chmod(0, dir) file[:path] = child file.stat.should be_nil # chmod it back so we can clean it up File.chmod(0777, dir) end + it "should return nil if parts of path are no directories" do + regular_file = tmpfile('ENOTDIR_test') + FileUtils.touch(regular_file) + impossible_child = File.join(regular_file, 'some_file') + + file[:path] = impossible_child + file.stat.should be_nil + end + it "should return the stat instance" do file.stat.should be_a(File::Stat) end it "should cache the stat instance" do file.stat.should equal(file.stat) end end describe "#write" do it "should propagate failures encountered when renaming the temporary file" do File.stubs(:open) File.expects(:rename).raises ArgumentError file[:backup] = 'puppet' file.stubs(:validate_checksum?).returns(false) property = stub('content_property', :actual_content => "something", :length => "something".length) file.stubs(:property).with(:content).returns(property) - lambda { file.write(:content) }.should raise_error(Puppet::Error) + expect { file.write(:content) }.to raise_error(Puppet::Error) end it "should delegate writing to the content property" do filehandle = stub_everything 'fh' File.stubs(:open).yields(filehandle) File.stubs(:rename) property = stub('content_property', :actual_content => "something", :length => "something".length) file[:backup] = 'puppet' file.stubs(:validate_checksum?).returns(false) file.stubs(:property).with(:content).returns(property) property.expects(:write).with(filehandle) file.write(:content) end describe "when validating the checksum" do before { file.stubs(:validate_checksum?).returns(true) } it "should fail if the checksum parameter and content checksums do not match" do checksum = stub('checksum_parameter', :sum => 'checksum_b', :sum_file => 'checksum_b') file.stubs(:parameter).with(:checksum).returns(checksum) property = stub('content_property', :actual_content => "something", :length => "something".length, :write => 'checksum_a') file.stubs(:property).with(:content).returns(property) - lambda { file.write :NOTUSED }.should raise_error(Puppet::Error) + expect { file.write :NOTUSED }.to raise_error(Puppet::Error) end end describe "when not validating the checksum" do before { file.stubs(:validate_checksum?).returns(false) } it "should not fail if the checksum property and content checksums do not match" do checksum = stub('checksum_parameter', :sum => 'checksum_b') file.stubs(:parameter).with(:checksum).returns(checksum) property = stub('content_property', :actual_content => "something", :length => "something".length, :write => 'checksum_a') file.stubs(:property).with(:content).returns(property) - lambda { file.write :NOTUSED }.should_not raise_error(Puppet::Error) + expect { file.write :NOTUSED }.to_not raise_error(Puppet::Error) end end end describe "#fail_if_checksum_is_wrong" do it "should fail if the checksum of the file doesn't match the expected one" do expect do file.instance_eval do parameter(:checksum).stubs(:sum_file).returns('wrong!!') fail_if_checksum_is_wrong(self[:path], 'anything!') end end.to raise_error(Puppet::Error, /File written to disk did not match checksum/) end it "should not fail if the checksum is correct" do file.instance_eval do parameter(:checksum).stubs(:sum_file).returns('anything!') fail_if_checksum_is_wrong(self[:path], 'anything!').should == nil end end it "should not fail if the checksum is absent" do file.instance_eval do parameter(:checksum).stubs(:sum_file).returns(nil) fail_if_checksum_is_wrong(self[:path], 'anything!').should == nil end end end describe "#write_content" do it "should delegate writing the file to the content property" do io = stub('io') file[:content] = "some content here" file.property(:content).expects(:write).with(io) file.send(:write_content, io) end end describe "#write_temporary_file?" do it "should be true if the file has specified content" do file[:content] = 'some content' file.send(:write_temporary_file?).should be_true end it "should be true if the file has specified source" do file[:source] = File.expand_path('/tmp/foo') file.send(:write_temporary_file?).should be_true end it "should be false if the file has neither content nor source" do file.send(:write_temporary_file?).should be_false end end describe "#property_fix" do { :mode => 0777, :owner => 'joeuser', :group => 'joeusers', :seluser => 'seluser', :selrole => 'selrole', :seltype => 'seltype', :selrange => 'selrange' }.each do |name,value| it "should sync the #{name} property if it's not in sync" do file[name] = value prop = file.property(name) prop.expects(:retrieve) prop.expects(:safe_insync?).returns false prop.expects(:sync) file.send(:property_fix) end end end describe "when autorequiring" do describe "target" do it "should require file resource when specified with the target property" do file = described_class.new(:path => File.expand_path("/foo"), :ensure => :directory) - link = described_class.new(:path => File.expand_path("/bar"), :ensure => :symlink, :target => File.expand_path("/foo")) + link = described_class.new(:path => File.expand_path("/bar"), :ensure => :link, :target => File.expand_path("/foo")) catalog.add_resource file catalog.add_resource link reqs = link.autorequire reqs.size.must == 1 reqs[0].source.must == file reqs[0].target.must == link end it "should require file resource when specified with the ensure property" do file = described_class.new(:path => File.expand_path("/foo"), :ensure => :directory) link = described_class.new(:path => File.expand_path("/bar"), :ensure => File.expand_path("/foo")) catalog.add_resource file catalog.add_resource link reqs = link.autorequire reqs.size.must == 1 reqs[0].source.must == file reqs[0].target.must == link end it "should not require target if target is not managed" do - link = described_class.new(:path => File.expand_path('/foo'), :ensure => :symlink, :target => '/bar') + link = described_class.new(:path => File.expand_path('/foo'), :ensure => :link, :target => '/bar') catalog.add_resource link link.autorequire.size.should == 0 end end describe "directories" do it "should autorequire its parent directory" do dir = described_class.new(:path => File.dirname(path)) catalog.add_resource file catalog.add_resource dir reqs = file.autorequire reqs[0].source.must == dir reqs[0].target.must == file end it "should autorequire its nearest ancestor directory" do dir = described_class.new(:path => File.dirname(path)) grandparent = described_class.new(:path => File.dirname(File.dirname(path))) catalog.add_resource file catalog.add_resource dir catalog.add_resource grandparent reqs = file.autorequire reqs.length.must == 1 reqs[0].source.must == dir reqs[0].target.must == file end it "should not autorequire anything when there is no nearest ancestor directory" do catalog.add_resource file file.autorequire.should be_empty end it "should not autorequire its parent dir if its parent dir is itself" do file[:path] = File.expand_path('/') catalog.add_resource file file.autorequire.should be_empty end describe "on Windows systems", :if => Puppet.features.microsoft_windows? do describe "when using UNC filenames" do it "should autorequire its parent directory" do file[:path] = '//server/foo/bar/baz' dir = described_class.new(:path => "//server/foo/bar") catalog.add_resource file catalog.add_resource dir reqs = file.autorequire reqs[0].source.must == dir reqs[0].target.must == file end it "should autorequire its nearest ancestor directory" do file = described_class.new(:path => "//server/foo/bar/baz/qux") dir = described_class.new(:path => "//server/foo/bar/baz") grandparent = described_class.new(:path => "//server/foo/bar") catalog.add_resource file catalog.add_resource dir catalog.add_resource grandparent reqs = file.autorequire reqs.length.must == 1 reqs[0].source.must == dir reqs[0].target.must == file end it "should not autorequire anything when there is no nearest ancestor directory" do file = described_class.new(:path => "//server/foo/bar/baz/qux") catalog.add_resource file file.autorequire.should be_empty end it "should not autorequire its parent dir if its parent dir is itself" do file = described_class.new(:path => "//server/foo") catalog.add_resource file puts file.autorequire file.autorequire.should be_empty end end end end end describe "when managing links" do require 'tempfile' if @real_posix describe "on POSIX systems" do before do Dir.mkdir(path) @target = File.join(path, "target") @link = File.join(path, "link") File.open(@target, "w", 0644) { |f| f.puts "yayness" } File.symlink(@target, @link) file[:path] = @link file[:mode] = 0755 catalog.add_resource file end it "should default to managing the link" do catalog.apply # I convert them to strings so they display correctly if there's an error. (File.stat(@target).mode & 007777).to_s(8).should == '644' end it "should be able to follow links" do file[:links] = :follow catalog.apply (File.stat(@target).mode & 007777).to_s(8).should == '755' end end else # @real_posix # should recode tests using expectations instead of using the filesystem end describe "on Microsoft Windows systems" do before do Puppet.features.stubs(:posix?).returns(false) Puppet.features.stubs(:microsoft_windows?).returns(true) end it "should refuse to work with links" end end describe "when using source" do before do file[:source] = File.expand_path('/one') end Puppet::Type::File::ParameterChecksum.value_collection.values.reject {|v| v == :none}.each do |checksum_type| describe "with checksum '#{checksum_type}'" do before do file[:checksum] = checksum_type end it 'should validate' do - lambda { file.validate }.should_not raise_error + expect { file.validate }.to_not raise_error end end end describe "with checksum 'none'" do before do file[:checksum] = :none end it 'should raise an exception when validating' do - lambda { file.validate }.should raise_error(/You cannot specify source when using checksum 'none'/) + expect { file.validate }.to raise_error(/You cannot specify source when using checksum 'none'/) end end end describe "when using content" do before do file[:content] = 'file contents' end (Puppet::Type::File::ParameterChecksum.value_collection.values - SOURCE_ONLY_CHECKSUMS).each do |checksum_type| describe "with checksum '#{checksum_type}'" do before do file[:checksum] = checksum_type end it 'should validate' do - lambda { file.validate }.should_not raise_error + expect { file.validate }.to_not raise_error end end end SOURCE_ONLY_CHECKSUMS.each do |checksum_type| describe "with checksum '#{checksum_type}'" do it 'should raise an exception when validating' do file[:checksum] = checksum_type - lambda { file.validate }.should raise_error(/You cannot specify content when using checksum '#{checksum_type}'/) + expect { file.validate }.to raise_error(/You cannot specify content when using checksum '#{checksum_type}'/) end end end end describe "when auditing" do before :each do # to prevent the catalog from trying to write state.yaml Puppet::Util::Storage.stubs(:store) end it "should not fail if creating a new file if group is not set" do file = described_class.new(:path => path, :audit => 'all', :content => 'content') catalog.add_resource(file) report = catalog.apply.report report.resource_statuses["File[#{path}]"].should_not be_failed File.read(path).should == 'content' end it "should not log errors if creating a new file with ensure present and no content" do file[:audit] = 'content' file[:ensure] = 'present' catalog.add_resource(file) catalog.apply File.should be_exist(path) @logs.should_not be_any {|l| l.level != :notice } end end describe "when specifying both source and checksum" do it 'should use the specified checksum when source is first' do file[:source] = File.expand_path('/foo') file[:checksum] = :md5lite file[:checksum].should == :md5lite end it 'should use the specified checksum when source is last' do file[:checksum] = :md5lite file[:source] = File.expand_path('/foo') file[:checksum].should == :md5lite end end describe "when validating" do [[:source, :target], [:source, :content], [:target, :content]].each do |prop1,prop2| it "should fail if both #{prop1} and #{prop2} are specified" do file[prop1] = prop1 == :source ? File.expand_path("prop1 value") : "prop1 value" file[prop2] = "prop2 value" expect do file.validate end.to raise_error(Puppet::Error, /You cannot specify more than one of/) end end end end diff --git a/spec/unit/type/user_spec.rb b/spec/unit/type/user_spec.rb index bb235b798..290fd6395 100755 --- a/spec/unit/type/user_spec.rb +++ b/spec/unit/type/user_spec.rb @@ -1,340 +1,340 @@ #!/usr/bin/env rspec require 'spec_helper' describe Puppet::Type.type(:user) do before :each do @provider_class = described_class.provide(:simple) do has_features :manages_expiry, :manages_password_age, :manages_passwords, :manages_solaris_rbac mk_resource_methods def create; end def delete; end def exists?; get(:ensure) != :absent; end def flush; end def self.instances; []; end end described_class.stubs(:defaultprovider).returns @provider_class end it "should be able to create a instance" do described_class.new(:name => "foo").should_not be_nil end it "should have an allows_duplicates feature" do described_class.provider_feature(:allows_duplicates).should_not be_nil end it "should have an manages_homedir feature" do described_class.provider_feature(:manages_homedir).should_not be_nil end it "should have an manages_passwords feature" do described_class.provider_feature(:manages_passwords).should_not be_nil end it "should have a manages_solaris_rbac feature" do described_class.provider_feature(:manages_solaris_rbac).should_not be_nil end it "should have a manages_expiry feature" do described_class.provider_feature(:manages_expiry).should_not be_nil end it "should have a manages_password_age feature" do described_class.provider_feature(:manages_password_age).should_not be_nil end it "should have a system_users feature" do described_class.provider_feature(:system_users).should_not be_nil end describe "instances" do it "should delegate existence questions to its provider" do @provider = @provider_class.new(:name => 'foo', :ensure => :absent) instance = described_class.new(:name => "foo", :provider => @provider) instance.exists?.should == false @provider.set(:ensure => :present) instance.exists?.should == true end end properties = [:ensure, :uid, :gid, :home, :comment, :shell, :password, :password_min_age, :password_max_age, :groups, :roles, :auths, :profiles, :project, :keys, :expiry] properties.each do |property| it "should have a #{property} property" do described_class.attrclass(property).ancestors.should be_include(Puppet::Property) end it "should have documentation for its #{property} property" do described_class.attrclass(property).doc.should be_instance_of(String) end end list_properties = [:groups, :roles, :auths] list_properties.each do |property| it "should have a list '#{property}'" do described_class.attrclass(property).ancestors.should be_include(Puppet::Property::List) end end it "should have an ordered list 'profiles'" do described_class.attrclass(:profiles).ancestors.should be_include(Puppet::Property::OrderedList) end it "should have key values 'keys'" do described_class.attrclass(:keys).ancestors.should be_include(Puppet::Property::KeyValue) end describe "when retrieving all current values" do before do @provider = @provider_class.new(:name => 'foo', :ensure => :present, :uid => 15, :gid => 15) @user = described_class.new(:name => "foo", :uid => 10, :provider => @provider) end it "should return a hash containing values for all set properties" do @user[:gid] = 10 values = @user.retrieve [@user.property(:uid), @user.property(:gid)].each { |property| values.should be_include(property) } end it "should set all values to :absent if the user is absent" do @user.property(:ensure).expects(:retrieve).returns :absent @user.property(:uid).expects(:retrieve).never @user.retrieve[@user.property(:uid)].should == :absent end it "should include the result of retrieving each property's current value if the user is present" do @user.retrieve[@user.property(:uid)].should == 15 end end describe "when managing the ensure property" do it "should support a :present value" do - lambda { described_class.new(:name => 'foo', :ensure => :present) }.should_not raise_error + expect { described_class.new(:name => 'foo', :ensure => :present) }.to_not raise_error end it "should support an :absent value" do - lambda { described_class.new(:name => 'foo', :ensure => :absent) }.should_not raise_error + expect { described_class.new(:name => 'foo', :ensure => :absent) }.to_not raise_error end it "should call :create on the provider when asked to sync to the :present state" do @provider = @provider_class.new(:name => 'foo', :ensure => :absent) @provider.expects(:create) described_class.new(:name => 'foo', :ensure => :present, :provider => @provider).parameter(:ensure).sync end it "should call :delete on the provider when asked to sync to the :absent state" do @provider = @provider_class.new(:name => 'foo', :ensure => :present) @provider.expects(:delete) described_class.new(:name => 'foo', :ensure => :absent, :provider => @provider).parameter(:ensure).sync end describe "and determining the current state" do it "should return :present when the provider indicates the user exists" do @provider = @provider_class.new(:name => 'foo', :ensure => :present) described_class.new(:name => 'foo', :ensure => :absent, :provider => @provider).parameter(:ensure).retrieve.should == :present end it "should return :absent when the provider indicates the user does not exist" do @provider = @provider_class.new(:name => 'foo', :ensure => :absent) described_class.new(:name => 'foo', :ensure => :present, :provider => @provider).parameter(:ensure).retrieve.should == :absent end end end describe "when managing the uid property" do it "should convert number-looking strings into actual numbers" do described_class.new(:name => 'foo', :uid => '50')[:uid].should == 50 end it "should support UIDs as numbers" do described_class.new(:name => 'foo', :uid => 50)[:uid].should == 50 end it "should support :absent as a value" do described_class.new(:name => 'foo', :uid => :absent)[:uid].should == :absent end end describe "when managing the gid" do it "should support :absent as a value" do described_class.new(:name => 'foo', :gid => :absent)[:gid].should == :absent end it "should convert number-looking strings into actual numbers" do described_class.new(:name => 'foo', :gid => '50')[:gid].should == 50 end it "should support GIDs specified as integers" do described_class.new(:name => 'foo', :gid => 50)[:gid].should == 50 end it "should support groups specified by name" do described_class.new(:name => 'foo', :gid => 'foo')[:gid].should == 'foo' end describe "when testing whether in sync" do it "should return true if no 'should' values are set" do # this is currently not the case because gid has no default value, so we would never even # call insync? on that property if param = described_class.new(:name => 'foo').parameter(:gid) param.must be_safe_insync(500) end end it "should return true if any of the specified groups are equal to the current integer" do Puppet::Util.expects(:gid).with("foo").returns 300 Puppet::Util.expects(:gid).with("bar").returns 500 described_class.new(:name => 'baz', :gid => [ 'foo', 'bar' ]).parameter(:gid).must be_safe_insync(500) end it "should return false if none of the specified groups are equal to the current integer" do Puppet::Util.expects(:gid).with("foo").returns 300 Puppet::Util.expects(:gid).with("bar").returns 500 described_class.new(:name => 'baz', :gid => [ 'foo', 'bar' ]).parameter(:gid).must_not be_safe_insync(700) end end describe "when syncing" do it "should use the first found, specified group as the desired value and send it to the provider" do Puppet::Util.expects(:gid).with("foo").returns nil Puppet::Util.expects(:gid).with("bar").returns 500 @provider = @provider_class.new(:name => 'foo') resource = described_class.new(:name => 'foo', :provider => @provider, :gid => [ 'foo', 'bar' ]) @provider.expects(:gid=).with 500 resource.parameter(:gid).sync end end end describe "when managing groups" do it "should support a singe group" do - lambda { described_class.new(:name => 'foo', :groups => 'bar') }.should_not raise_error + expect { described_class.new(:name => 'foo', :groups => 'bar') }.to_not raise_error end it "should support multiple groups as an array" do - lambda { described_class.new(:name => 'foo', :groups => [ 'bar' ]) }.should_not raise_error - lambda { described_class.new(:name => 'foo', :groups => [ 'bar', 'baz' ]) }.should_not raise_error + expect { described_class.new(:name => 'foo', :groups => [ 'bar' ]) }.to_not raise_error + expect { described_class.new(:name => 'foo', :groups => [ 'bar', 'baz' ]) }.to_not raise_error end it "should not support a comma separated list" do - lambda { described_class.new(:name => 'foo', :groups => 'bar,baz') }.should raise_error(Puppet::Error, /Group names must be provided as an array/) + expect { described_class.new(:name => 'foo', :groups => 'bar,baz') }.to raise_error(Puppet::Error, /Group names must be provided as an array/) end it "should not support an empty string" do - lambda { described_class.new(:name => 'foo', :groups => '') }.should raise_error(Puppet::Error, /Group names must not be empty/) + expect { described_class.new(:name => 'foo', :groups => '') }.to raise_error(Puppet::Error, /Group names must not be empty/) end describe "when testing is in sync" do before :each do # the useradd provider uses a single string to represent groups and so does Puppet::Property::List when converting to should values @provider = @provider_class.new(:name => 'foo', :groups => 'a,b,e,f') end it "should not care about order" do @property = described_class.new(:name => 'foo', :groups => [ 'a', 'c', 'b' ]).property(:groups) @property.must be_safe_insync([ 'a', 'b', 'c' ]) @property.must be_safe_insync([ 'a', 'c', 'b' ]) @property.must be_safe_insync([ 'b', 'a', 'c' ]) @property.must be_safe_insync([ 'b', 'c', 'a' ]) @property.must be_safe_insync([ 'c', 'a', 'b' ]) @property.must be_safe_insync([ 'c', 'b', 'a' ]) end it "should merge current value and desired value if membership minimal" do @instance = described_class.new(:name => 'foo', :groups => [ 'a', 'c', 'b' ], :provider => @provider) @instance[:membership] = :minimum @instance[:groups].should == 'a,b,c,e,f' end it "should not treat a subset of groups insync if membership inclusive" do @instance = described_class.new(:name => 'foo', :groups => [ 'a', 'c', 'b' ], :provider => @provider) @instance[:membership] = :inclusive @instance[:groups].should == 'a,b,c' end end end describe "when managing expiry" do it "should fail if given an invalid date" do - lambda { described_class.new(:name => 'foo', :expiry => "200-20-20") }.should raise_error(Puppet::Error, /Expiry dates must be YYYY-MM-DD/) + expect { described_class.new(:name => 'foo', :expiry => "200-20-20") }.to raise_error(Puppet::Error, /Expiry dates must be YYYY-MM-DD/) end end describe "when managing minimum password age" do it "should accept a negative minimum age" do - expect { described_class.new(:name => 'foo', :password_min_age => '-1') }.should_not raise_error + expect { described_class.new(:name => 'foo', :password_min_age => '-1') }.to_not raise_error end it "should fail with an empty minimum age" do - expect { described_class.new(:name => 'foo', :password_min_age => '') }.should raise_error(Puppet::Error, /minimum age must be provided as a number/) + expect { described_class.new(:name => 'foo', :password_min_age => '') }.to raise_error(Puppet::Error, /minimum age must be provided as a number/) end end describe "when managing maximum password age" do it "should accept a negative maximum age" do - expect { described_class.new(:name => 'foo', :password_max_age => '-1') }.should_not raise_error + expect { described_class.new(:name => 'foo', :password_max_age => '-1') }.to_not raise_error end it "should fail with an empty maximum age" do - expect { described_class.new(:name => 'foo', :password_max_age => '') }.should raise_error(Puppet::Error, /maximum age must be provided as a number/) + expect { described_class.new(:name => 'foo', :password_max_age => '') }.to raise_error(Puppet::Error, /maximum age must be provided as a number/) end end describe "when managing passwords" do before do @password = described_class.new(:name => 'foo', :password => 'mypass').parameter(:password) end it "should not include the password in the change log when adding the password" do @password.change_to_s(:absent, "mypass").should_not be_include("mypass") end it "should not include the password in the change log when changing the password" do @password.change_to_s("other", "mypass").should_not be_include("mypass") end it "should redact the password when displaying the old value" do @password.is_to_s("currentpassword").should =~ /^\[old password hash redacted\]$/ end it "should redact the password when displaying the new value" do @password.should_to_s("newpassword").should =~ /^\[new password hash redacted\]$/ end it "should fail if a ':' is included in the password" do - lambda { described_class.new(:name => 'foo', :password => "some:thing") }.should raise_error(Puppet::Error, /Passwords cannot include ':'/) + expect { described_class.new(:name => 'foo', :password => "some:thing") }.to raise_error(Puppet::Error, /Passwords cannot include ':'/) end it "should allow the value to be set to :absent" do - lambda { described_class.new(:name => 'foo', :password => :absent) }.should_not raise_error + expect { described_class.new(:name => 'foo', :password => :absent) }.to_not raise_error end end describe "when manages_solaris_rbac is enabled" do it "should support a :role value for ensure" do - lambda { described_class.new(:name => 'foo', :ensure => :role) }.should_not raise_error + expect { described_class.new(:name => 'foo', :ensure => :role) }.to_not raise_error end end describe "when user has roles" do it "should autorequire roles" do testuser = described_class.new(:name => "testuser", :roles => ['testrole'] ) testrole = described_class.new(:name => "testrole") config = Puppet::Resource::Catalog.new :testing do |conf| [testuser, testrole].each { |resource| conf.add_resource resource } end Puppet::Type::User::ProviderDirectoryservice.stubs(:get_macosx_version_major).returns "10.5" rel = testuser.autorequire[0] rel.source.ref.should == testrole.ref rel.target.ref.should == testuser.ref end end end diff --git a/spec/unit/type_spec.rb b/spec/unit/type_spec.rb index 5a82e35b3..57ced3bfb 100755 --- a/spec/unit/type_spec.rb +++ b/spec/unit/type_spec.rb @@ -1,774 +1,774 @@ #!/usr/bin/env rspec require 'spec_helper' describe Puppet::Type, :fails_on_windows => true do include PuppetSpec::Files it "should be Comparable" do a = Puppet::Type.type(:notify).new(:name => "a") b = Puppet::Type.type(:notify).new(:name => "b") c = Puppet::Type.type(:notify).new(:name => "c") [[a, b, c], [a, c, b], [b, a, c], [b, c, a], [c, a, b], [c, b, a]].each do |this| this.sort.should == [a, b, c] end a.must be < b a.must be < c b.must be > a b.must be < c c.must be > a c.must be > b [a, b, c].each {|x| a.must be <= x } [a, b, c].each {|x| c.must be >= x } b.must be_between(a, c) end it "should consider a parameter to be valid if it is a valid parameter" do Puppet::Type.type(:mount).should be_valid_parameter(:path) end it "should consider a parameter to be valid if it is a valid property" do Puppet::Type.type(:mount).should be_valid_parameter(:fstype) end it "should consider a parameter to be valid if it is a valid metaparam" do Puppet::Type.type(:mount).should be_valid_parameter(:noop) end it "should be able to retrieve a property by name" do resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present) resource.property(:fstype).must be_instance_of(Puppet::Type.type(:mount).attrclass(:fstype)) end it "should be able to retrieve a parameter by name" do resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present) resource.parameter(:name).must be_instance_of(Puppet::Type.type(:mount).attrclass(:name)) end it "should be able to retrieve a property by name using the :parameter method" do resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present) resource.parameter(:fstype).must be_instance_of(Puppet::Type.type(:mount).attrclass(:fstype)) end it "should be able to retrieve all set properties" do resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present) props = resource.properties props.should_not be_include(nil) [:fstype, :ensure, :pass].each do |name| props.should be_include(resource.parameter(name)) end end it "should have a method for setting default values for resources" do Puppet::Type.type(:mount).new(:name => "foo").must respond_to(:set_default) end it "should do nothing for attributes that have no defaults and no specified value" do Puppet::Type.type(:mount).new(:name => "foo").parameter(:noop).should be_nil end it "should have a method for adding tags" do Puppet::Type.type(:mount).new(:name => "foo").must respond_to(:tags) end it "should use the tagging module" do Puppet::Type.type(:mount).ancestors.should be_include(Puppet::Util::Tagging) end it "should delegate to the tagging module when tags are added" do resource = Puppet::Type.type(:mount).new(:name => "foo") resource.stubs(:tag).with(:mount) resource.expects(:tag).with(:tag1, :tag2) resource.tags = [:tag1,:tag2] end it "should add the current type as tag" do resource = Puppet::Type.type(:mount).new(:name => "foo") resource.stubs(:tag) resource.expects(:tag).with(:mount) resource.tags = [:tag1,:tag2] end it "should have a method to know if the resource is exported" do Puppet::Type.type(:mount).new(:name => "foo").must respond_to(:exported?) end it "should have a method to know if the resource is virtual" do Puppet::Type.type(:mount).new(:name => "foo").must respond_to(:virtual?) end it "should consider its version to be its catalog version" do resource = Puppet::Type.type(:mount).new(:name => "foo") catalog = Puppet::Resource::Catalog.new catalog.version = 50 catalog.add_resource resource resource.version.should == 50 end it "should consider its version to be zero if it has no catalog" do Puppet::Type.type(:mount).new(:name => "foo").version.should == 0 end it "should provide source_descriptors" do resource = Puppet::Type.type(:mount).new(:name => "foo") catalog = Puppet::Resource::Catalog.new catalog.version = 50 catalog.add_resource resource resource.source_descriptors.should == {:tags=>["mount", "foo"], :path=>"/Mount[foo]"} end it "should consider its type to be the name of its class" do Puppet::Type.type(:mount).new(:name => "foo").type.should == :mount end it "should use any provided noop value" do Puppet::Type.type(:mount).new(:name => "foo", :noop => true).must be_noop end it "should use the global noop value if none is provided" do Puppet[:noop] = true Puppet::Type.type(:mount).new(:name => "foo").must be_noop end it "should not be noop if in a non-host_config catalog" do resource = Puppet::Type.type(:mount).new(:name => "foo") catalog = Puppet::Resource::Catalog.new catalog.add_resource resource resource.should_not be_noop end describe "when creating an event" do before do @resource = Puppet::Type.type(:mount).new :name => "foo" end it "should have the resource's reference as the resource" do @resource.event.resource.should == "Mount[foo]" end it "should have the resource's log level as the default log level" do @resource[:loglevel] = :warning @resource.event.default_log_level.should == :warning end {:file => "/my/file", :line => 50, :tags => %{foo bar}}.each do |attr, value| it "should set the #{attr}" do @resource.stubs(attr).returns value @resource.event.send(attr).should == value end end it "should allow specification of event attributes" do @resource.event(:status => "noop").status.should == "noop" end end describe "when creating a provider" do before :each do @type = Puppet::Type.newtype(:provider_test_type) do newparam(:name) { isnamevar } newparam(:foo) newproperty(:bar) end end after :each do @type.provider_hash.clear end describe "when determining if instances of the type are managed" do it "should not consider audit only resources to be managed" do @type.new(:name => "foo", :audit => 'all').managed?.should be_false end it "should not consider resources with only parameters to be managed" do @type.new(:name => "foo", :foo => 'did someone say food?').managed?.should be_false end it "should consider resources with any properties set to be managed" do @type.new(:name => "foo", :bar => 'Let us all go there').managed?.should be_true end end it "should have documentation for the 'provider' parameter if there are providers" do @type.provide(:test_provider) @type.paramdoc(:provider).should =~ /`provider_test_type`[\s\r]+resource/ end it "should not have documentation for the 'provider' parameter if there are no providers" do expect { @type.paramdoc(:provider) }.to raise_error(NoMethodError) end it "should create a subclass of Puppet::Provider for the provider" do provider = @type.provide(:test_provider) provider.ancestors.should include(Puppet::Provider) end it "should use a parent class if specified" do parent_provider = @type.provide(:parent_provider) child_provider = @type.provide(:child_provider, :parent => parent_provider) child_provider.ancestors.should include(parent_provider) end it "should use a parent class if specified by name" do parent_provider = @type.provide(:parent_provider) child_provider = @type.provide(:child_provider, :parent => :parent_provider) child_provider.ancestors.should include(parent_provider) end it "should raise an error when the parent class can't be found" do expect { @type.provide(:child_provider, :parent => :parent_provider) }.to raise_error(Puppet::DevError, /Could not find parent provider.+parent_provider/) end it "should ensure its type has a 'provider' parameter" do @type.provide(:test_provider) @type.parameters.should include(:provider) end it "should remove a previously registered provider with the same name" do old_provider = @type.provide(:test_provider) new_provider = @type.provide(:test_provider) old_provider.should_not equal(new_provider) end it "should register itself as a provider for the type" do provider = @type.provide(:test_provider) provider.should == @type.provider(:test_provider) end it "should create a provider when a provider with the same name previously failed" do @type.provide(:test_provider) do raise "failed to create this provider" end rescue nil provider = @type.provide(:test_provider) provider.ancestors.should include(Puppet::Provider) provider.should == @type.provider(:test_provider) end end describe "when choosing a default provider" do it "should choose the provider with the highest specificity" do # Make a fake type type = Puppet::Type.newtype(:defaultprovidertest) do newparam(:name) do end end basic = type.provide(:basic) {} greater = type.provide(:greater) {} basic.stubs(:specificity).returns 1 greater.stubs(:specificity).returns 2 type.defaultprovider.should equal(greater) end end describe "when initializing" do describe "and passed a TransObject" do it "should fail" do trans = Puppet::TransObject.new("/foo", :mount) - lambda { Puppet::Type.type(:mount).new(trans) }.should raise_error(Puppet::DevError) + expect { Puppet::Type.type(:mount).new(trans) }.to raise_error(Puppet::DevError) end end describe "and passed a Puppet::Resource instance" do it "should set its title to the title of the resource if the resource type is equal to the current type" do resource = Puppet::Resource.new(:mount, "/foo", :parameters => {:name => "/other"}) Puppet::Type.type(:mount).new(resource).title.should == "/foo" end it "should set its title to the resource reference if the resource type is not equal to the current type" do resource = Puppet::Resource.new(:user, "foo") Puppet::Type.type(:mount).new(resource).title.should == "User[foo]" end [:line, :file, :catalog, :exported, :virtual].each do |param| it "should copy '#{param}' from the resource if present" do resource = Puppet::Resource.new(:mount, "/foo") resource.send(param.to_s + "=", "foo") resource.send(param.to_s + "=", "foo") Puppet::Type.type(:mount).new(resource).send(param).should == "foo" end end it "should copy any tags from the resource" do resource = Puppet::Resource.new(:mount, "/foo") resource.tag "one", "two" tags = Puppet::Type.type(:mount).new(resource).tags tags.should be_include("one") tags.should be_include("two") end it "should copy the resource's parameters as its own" do resource = Puppet::Resource.new(:mount, "/foo", :parameters => {:atboot => true, :fstype => "boo"}) params = Puppet::Type.type(:mount).new(resource).to_hash params[:fstype].should == "boo" params[:atboot].should == true end end describe "and passed a Hash" do it "should extract the title from the hash" do Puppet::Type.type(:mount).new(:title => "/yay").title.should == "/yay" end it "should work when hash keys are provided as strings" do Puppet::Type.type(:mount).new("title" => "/yay").title.should == "/yay" end it "should work when hash keys are provided as symbols" do Puppet::Type.type(:mount).new(:title => "/yay").title.should == "/yay" end it "should use the name from the hash as the title if no explicit title is provided" do Puppet::Type.type(:mount).new(:name => "/yay").title.should == "/yay" end it "should use the Resource Type's namevar to determine how to find the name in the hash" do yay = make_absolute('/yay') Puppet::Type.type(:file).new(:path => yay).title.should == yay end [:catalog].each do |param| it "should extract '#{param}' from the hash if present" do Puppet::Type.type(:mount).new(:name => "/yay", param => "foo").send(param).should == "foo" end end it "should use any remaining hash keys as its parameters" do resource = Puppet::Type.type(:mount).new(:title => "/foo", :catalog => "foo", :atboot => true, :fstype => "boo") resource[:fstype].must == "boo" resource[:atboot].must == true end end it "should fail if any invalid attributes have been provided" do - lambda { Puppet::Type.type(:mount).new(:title => "/foo", :nosuchattr => "whatever") }.should raise_error(Puppet::Error) + expect { Puppet::Type.type(:mount).new(:title => "/foo", :nosuchattr => "whatever") }.to raise_error(Puppet::Error) end it "should set its name to the resource's title if the resource does not have a :name or namevar parameter set" do resource = Puppet::Resource.new(:mount, "/foo") Puppet::Type.type(:mount).new(resource).name.should == "/foo" end it "should fail if no title, name, or namevar are provided" do - lambda { Puppet::Type.type(:file).new(:atboot => true) }.should raise_error(Puppet::Error) + expect { Puppet::Type.type(:file).new(:atboot => true) }.to raise_error(Puppet::Error) end it "should set the attributes in the order returned by the class's :allattrs method" do Puppet::Type.type(:mount).stubs(:allattrs).returns([:name, :atboot, :noop]) resource = Puppet::Resource.new(:mount, "/foo", :parameters => {:name => "myname", :atboot => "myboot", :noop => "whatever"}) set = [] Puppet::Type.type(:mount).any_instance.stubs(:newattr).with do |param, hash| set << param true end.returns(stub_everything("a property")) Puppet::Type.type(:mount).new(resource) set[-1].should == :noop set[-2].should == :atboot end it "should always set the name and then default provider before anything else" do Puppet::Type.type(:mount).stubs(:allattrs).returns([:provider, :name, :atboot]) resource = Puppet::Resource.new(:mount, "/foo", :parameters => {:name => "myname", :atboot => "myboot"}) set = [] Puppet::Type.type(:mount).any_instance.stubs(:newattr).with do |param, hash| set << param true end.returns(stub_everything("a property")) Puppet::Type.type(:mount).new(resource) set[0].should == :name set[1].should == :provider end # This one is really hard to test :/ it "should set each default immediately if no value is provided" do defaults = [] Puppet::Type.type(:service).any_instance.stubs(:set_default).with { |value| defaults << value; true } Puppet::Type.type(:service).new :name => "whatever" defaults[0].should == :provider end it "should retain a copy of the originally provided parameters" do Puppet::Type.type(:mount).new(:name => "foo", :atboot => true, :noop => false).original_parameters.should == {:atboot => true, :noop => false} end it "should delete the name via the namevar from the originally provided parameters" do Puppet::Type.type(:file).new(:name => make_absolute('/foo')).original_parameters[:path].should be_nil end end it "should have a class method for converting a hash into a Puppet::Resource instance" do Puppet::Type.type(:mount).must respond_to(:hash2resource) end describe "when converting a hash to a Puppet::Resource instance" do before do @type = Puppet::Type.type(:mount) end it "should treat a :title key as the title of the resource" do @type.hash2resource(:name => "/foo", :title => "foo").title.should == "foo" end it "should use the name from the hash as the title if no explicit title is provided" do @type.hash2resource(:name => "foo").title.should == "foo" end it "should use the Resource Type's namevar to determine how to find the name in the hash" do @type.stubs(:key_attributes).returns([ :myname ]) @type.hash2resource(:myname => "foo").title.should == "foo" end [:catalog].each do |attr| it "should use any provided #{attr}" do @type.hash2resource(:name => "foo", attr => "eh").send(attr).should == "eh" end end it "should set all provided parameters on the resource" do @type.hash2resource(:name => "foo", :fstype => "boo", :boot => "fee").to_hash.should == {:name => "foo", :fstype => "boo", :boot => "fee"} end it "should not set the title as a parameter on the resource" do @type.hash2resource(:name => "foo", :title => "eh")[:title].should be_nil end it "should not set the catalog as a parameter on the resource" do @type.hash2resource(:name => "foo", :catalog => "eh")[:catalog].should be_nil end it "should treat hash keys equivalently whether provided as strings or symbols" do resource = @type.hash2resource("name" => "foo", "title" => "eh", "fstype" => "boo") resource.title.should == "eh" resource[:name].should == "foo" resource[:fstype].should == "boo" end end describe "when retrieving current property values" do before do @resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present) @resource.property(:ensure).stubs(:retrieve).returns :absent end it "should fail if its provider is unsuitable" do @resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present) @resource.provider.class.expects(:suitable?).returns false - lambda { @resource.retrieve_resource }.should raise_error(Puppet::Error) + expect { @resource.retrieve_resource }.to raise_error(Puppet::Error) end it "should return a Puppet::Resource instance with its type and title set appropriately" do result = @resource.retrieve_resource result.should be_instance_of(Puppet::Resource) result.type.should == "Mount" result.title.should == "foo" end it "should set the name of the returned resource if its own name and title differ" do @resource[:name] = "my name" @resource.title = "other name" @resource.retrieve_resource[:name].should == "my name" end it "should provide a value for all set properties" do values = @resource.retrieve_resource [:ensure, :fstype, :pass].each { |property| values[property].should_not be_nil } end it "should provide a value for 'ensure' even if no desired value is provided" do @resource = Puppet::Type.type(:file).new(:path => make_absolute("/my/file/that/can't/exist")) end it "should not call retrieve on non-ensure properties if the resource is absent and should consider the property absent" do @resource.property(:ensure).expects(:retrieve).returns :absent @resource.property(:fstype).expects(:retrieve).never @resource.retrieve_resource[:fstype].should == :absent end it "should include the result of retrieving each property's current value if the resource is present" do @resource.property(:ensure).expects(:retrieve).returns :present @resource.property(:fstype).expects(:retrieve).returns 15 @resource.retrieve_resource[:fstype] == 15 end end describe ".title_patterns" do describe "when there's one namevar" do before do @type_class = Puppet::Type.type(:notify) @type_class.stubs(:key_attributes).returns([:one]) end it "should have a default pattern for when there's one namevar" do patterns = @type_class.title_patterns patterns.length.should == 1 patterns[0].length.should == 2 end it "should have a regexp that captures the entire string" do patterns = @type_class.title_patterns string = "abc\n\tdef" patterns[0][0] =~ string $1.should == "abc\n\tdef" end end end describe "when in a catalog" do before do @catalog = Puppet::Resource::Catalog.new @container = Puppet::Type.type(:component).new(:name => "container") @one = Puppet::Type.type(:file).new(:path => make_absolute("/file/one")) @two = Puppet::Type.type(:file).new(:path => make_absolute("/file/two")) @catalog.add_resource @container @catalog.add_resource @one @catalog.add_resource @two @catalog.add_edge @container, @one @catalog.add_edge @container, @two end it "should have no parent if there is no in edge" do @container.parent.should be_nil end it "should set its parent to its in edge" do @one.parent.ref.should == @container.ref end after do @catalog.clear(true) end end it "should have a 'stage' metaparam" do Puppet::Type.metaparamclass(:stage).should be_instance_of(Class) end describe "#suitable?" do let(:type) { Puppet::Type.type(:file) } let(:resource) { type.new :path => tmpfile('suitable') } let(:provider) { resource.provider } it "should be suitable if its type doesn't use providers" do type.stubs(:paramclass).with(:provider).returns nil resource.must be_suitable end it "should be suitable if it has a provider which is suitable" do resource.must be_suitable end it "should not be suitable if it has a provider which is not suitable" do provider.class.stubs(:suitable?).returns false resource.should_not be_suitable end it "should be suitable if it does not have a provider and there is a default provider" do resource.stubs(:provider).returns nil resource.must be_suitable end it "should not be suitable if it doesn't have a provider and there is not default provider" do resource.stubs(:provider).returns nil type.stubs(:defaultprovider).returns nil resource.should_not be_suitable end end describe "::instances" do it "should not fail if no suitable providers are found" do fake_type = Puppet::Type.newtype(:type_spec_fake_type) do newparam(:name) do isnamevar end newproperty(:prop1) do end provide(:fake1) do confine :exists => '/no/such/file' mk_resource_methods end end - expect { fake_type.instances }.should_not raise_error + expect { fake_type.instances }.to_not raise_error end end describe "::ensurable?" do before :each do class TestEnsurableType < Puppet::Type def exists?; end def create; end def destroy; end end end it "is true if the class has exists?, create, and destroy methods defined" do TestEnsurableType.should be_ensurable end it "is false if exists? is not defined" do TestEnsurableType.class_eval { remove_method(:exists?) } TestEnsurableType.should_not be_ensurable end it "is false if create is not defined" do TestEnsurableType.class_eval { remove_method(:create) } TestEnsurableType.should_not be_ensurable end it "is false if destroy is not defined" do TestEnsurableType.class_eval { remove_method(:destroy) } TestEnsurableType.should_not be_ensurable end end end describe Puppet::Type::RelationshipMetaparam do include PuppetSpec::Files it "should be a subclass of Puppet::Parameter" do Puppet::Type::RelationshipMetaparam.superclass.should equal(Puppet::Parameter) end it "should be able to produce a list of subclasses" do Puppet::Type::RelationshipMetaparam.should respond_to(:subclasses) end describe "when munging relationships", :fails_on_windows => true do before do @path = make_absolute('/foo') @resource = Puppet::Type.type(:mount).new :name => @path @metaparam = Puppet::Type.metaparamclass(:require).new :resource => @resource end it "should accept Puppet::Resource instances" do ref = Puppet::Resource.new(:file, @path) @metaparam.munge(ref)[0].should equal(ref) end it "should turn any string into a Puppet::Resource" do @metaparam.munge("File[/ref]")[0].should be_instance_of(Puppet::Resource) end end it "should be able to validate relationships" do Puppet::Type.metaparamclass(:require).new(:resource => mock("resource")).should respond_to(:validate_relationship) end it "should fail if any specified resource is not found in the catalog" do catalog = mock 'catalog' resource = stub 'resource', :catalog => catalog, :ref => "resource" param = Puppet::Type.metaparamclass(:require).new(:resource => resource, :value => %w{Foo[bar] Class[test]}) catalog.expects(:resource).with("Foo[bar]").returns "something" catalog.expects(:resource).with("Class[Test]").returns nil param.expects(:fail).with { |string| string.include?("Class[Test]") } param.validate_relationship end end describe Puppet::Type.metaparamclass(:check) do include PuppetSpec::Files it "should warn and create an instance of ':audit'" do file = Puppet::Type.type(:file).new :path => make_absolute('/foo') file.expects(:warning) file[:check] = :mode file[:audit].should == [:mode] end end describe Puppet::Type.metaparamclass(:audit) do include PuppetSpec::Files before do @resource = Puppet::Type.type(:file).new :path => make_absolute('/foo') end it "should default to being nil" do @resource[:audit].should be_nil end it "should specify all possible properties when asked to audit all properties" do @resource[:audit] = :all list = @resource.class.properties.collect { |p| p.name } @resource[:audit].should == list end it "should accept the string 'all' to specify auditing all possible properties" do @resource[:audit] = 'all' list = @resource.class.properties.collect { |p| p.name } @resource[:audit].should == list end it "should fail if asked to audit an invalid property" do - lambda { @resource[:audit] = :foobar }.should raise_error(Puppet::Error) + expect { @resource[:audit] = :foobar }.to raise_error(Puppet::Error) end it "should create an attribute instance for each auditable property" do @resource[:audit] = :mode @resource.parameter(:mode).should_not be_nil end it "should accept properties specified as a string" do @resource[:audit] = "mode" @resource.parameter(:mode).should_not be_nil end it "should not create attribute instances for parameters, only properties" do @resource[:audit] = :noop @resource.parameter(:noop).should be_nil end describe "when generating the uniqueness key" do it "should include all of the key_attributes in alphabetical order by attribute name" do Puppet::Type.type(:file).stubs(:key_attributes).returns [:path, :mode, :owner] Puppet::Type.type(:file).stubs(:title_patterns).returns( [ [ /(.*)/, [ [:path, lambda{|x| x} ] ] ] ] ) myfile = make_absolute('/my/file') res = Puppet::Type.type(:file).new( :title => myfile, :path => myfile, :owner => 'root', :content => 'hello' ) res.uniqueness_key.should == [ nil, 'root', myfile] end end end diff --git a/spec/unit/util/autoload_spec.rb b/spec/unit/util/autoload_spec.rb index 47ee54e1f..e243b6c18 100755 --- a/spec/unit/util/autoload_spec.rb +++ b/spec/unit/util/autoload_spec.rb @@ -1,123 +1,129 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/util/autoload' describe Puppet::Util::Autoload do include PuppetSpec::Files before do @autoload = Puppet::Util::Autoload.new("foo", "tmp") @autoload.stubs(:eachdir).yields "/my/dir" end describe "when building the search path" do before :each do @dira = make_absolute('/a') @dirb = make_absolute('/b') @dirc = make_absolute('/c') end it "should collect all of the plugins and lib directories that exist in the current environment's module path" do Puppet.settings.expects(:value).with(:environment).returns "foo" Puppet.settings.expects(:value).with(:modulepath, :foo).returns "#{@dira}#{File::PATH_SEPARATOR}#{@dirb}#{File::PATH_SEPARATOR}#{@dirc}" Dir.expects(:entries).with(@dira).returns %w{one two} Dir.expects(:entries).with(@dirb).returns %w{one two} FileTest.stubs(:directory?).returns false FileTest.expects(:directory?).with(@dira).returns true FileTest.expects(:directory?).with(@dirb).returns true ["#{@dira}/one/plugins", "#{@dira}/two/lib", "#{@dirb}/one/plugins", "#{@dirb}/two/lib"].each do |d| FileTest.expects(:directory?).with(d).returns true end @autoload.module_directories.should == ["#{@dira}/one/plugins", "#{@dira}/two/lib", "#{@dirb}/one/plugins", "#{@dirb}/two/lib"] end it "should not look for lib directories in directories starting with '.'" do Puppet.settings.expects(:value).with(:environment).returns "foo" Puppet.settings.expects(:value).with(:modulepath, :foo).returns @dira Dir.expects(:entries).with(@dira).returns %w{. ..} FileTest.expects(:directory?).with(@dira).returns true FileTest.expects(:directory?).with("#{@dira}/./lib").never FileTest.expects(:directory?).with("#{@dira}/./plugins").never FileTest.expects(:directory?).with("#{@dira}/../lib").never FileTest.expects(:directory?).with("#{@dira}/../plugins").never @autoload.module_directories end it "should include the module directories, the Puppet libdir, and all of the Ruby load directories" do Puppet.stubs(:[]).with(:libdir).returns(%w{/libdir1 /lib/dir/two /third/lib/dir}.join(File::PATH_SEPARATOR)) @autoload.expects(:module_directories).returns %w{/one /two} @autoload.search_directories.should == %w{/one /two /libdir1 /lib/dir/two /third/lib/dir} + $LOAD_PATH end it "should include in its search path all of the unique search directories that have a subdirectory matching the autoload path" do @autoload = Puppet::Util::Autoload.new("foo", "loaddir") @autoload.expects(:search_directories).returns %w{/one /two /three /three} FileTest.expects(:directory?).with("/one/loaddir").returns true FileTest.expects(:directory?).with("/two/loaddir").returns false FileTest.expects(:directory?).with("/three/loaddir").returns true @autoload.searchpath.should == ["/one/loaddir", "/three/loaddir"] end end it "should include its FileCache module" do Puppet::Util::Autoload.ancestors.should be_include(Puppet::Util::Autoload::FileCache) end describe "when loading a file" do before do @autoload.stubs(:searchpath).returns %w{/a} end [RuntimeError, LoadError, SyntaxError].each do |error| it "should die with Puppet::Error if a #{error.to_s} exception is thrown" do @autoload.stubs(:file_exist?).returns true Kernel.expects(:load).raises error lambda { @autoload.load("foo") }.should raise_error(Puppet::Error) end end it "should not raise an error if the file is missing" do @autoload.load("foo").should == false end it "should register loaded files with the main loaded file list so they are not reloaded by ruby" do @autoload.stubs(:file_exist?).returns true Kernel.stubs(:load) @autoload.load("myfile") $LOADED_FEATURES.should be_include("tmp/myfile.rb") end end describe "when loading all files" do before do @autoload.stubs(:searchpath).returns %w{/a} - Dir.stubs(:glob).returns "/path/to/file.rb" - - @autoload.class.stubs(:loaded?).returns(false) + @filename = rand(10000).to_s + ".rb" + Dir.stubs(:glob).returns(@filename) end [RuntimeError, LoadError, SyntaxError].each do |error| - it "should die an if a #{error.to_s} exception is thrown", :'fails_on_ruby_1.9.2' => true do + it "dies if a #{error.to_s} exception is thrown", :'fails_on_ruby_1.9.2' => true do Kernel.expects(:require).raises error - lambda { @autoload.loadall }.should raise_error(Puppet::Error) + expect { @autoload.loadall }.to raise_error(Puppet::Error) end end - it "should require the full path to the file", :'fails_on_ruby_1.9.2' => true do - Kernel.expects(:require).with("/path/to/file.rb") + it "requires the file", :'fails_on_ruby_1.9.2' => true do + Kernel.expects(:require).with(@filename) @autoload.loadall end + + it "marks the file as required even if loading fails", :'fails_on_ruby_1.9.2' => true do + Dir.stubs(:glob).returns "/this/file/does/not/exist.rb" + + expect { @autoload.loadall }.to raise_error(Puppet::Error) + expect { @autoload.loadall }.not_to raise_error + end end end diff --git a/spec/unit/util/suidmanager_spec.rb b/spec/unit/util/suidmanager_spec.rb index 187754ab6..bcdd37638 100755 --- a/spec/unit/util/suidmanager_spec.rb +++ b/spec/unit/util/suidmanager_spec.rb @@ -1,283 +1,283 @@ #!/usr/bin/env rspec require 'spec_helper' describe Puppet::Util::SUIDManager do let :user do Puppet::Type.type(:user).new(:name => 'name', :uid => 42, :gid => 42) end let :xids do Hash.new {|h,k| 0} end before :each do Puppet::Util::SUIDManager.stubs(:convert_xid).returns(42) pwent = stub('pwent', :name => 'fred', :uid => 42, :gid => 42) Etc.stubs(:getpwuid).with(42).returns(pwent) [:euid, :egid, :uid, :gid, :groups].each do |id| Process.stubs("#{id}=").with {|value| xids[id] = value } end end describe "#initgroups" do it "should use the primary group of the user as the 'basegid'" do Process.expects(:initgroups).with('fred', 42) described_class.initgroups(42) end end describe "#uid" do it "should allow setting euid/egid" do Puppet::Util::SUIDManager.egid = user[:gid] Puppet::Util::SUIDManager.euid = user[:uid] xids[:egid].should == user[:gid] xids[:euid].should == user[:uid] end end describe "#asuser" do it "should not get or set euid/egid when not root" do Puppet.features.stubs(:microsoft_windows?).returns(false) Process.stubs(:uid).returns(1) Process.stubs(:egid).returns(51) Process.stubs(:euid).returns(50) Puppet::Util::SUIDManager.asuser(user[:uid], user[:gid]) {} xids.should be_empty end context "when root and not windows" do before :each do Process.stubs(:uid).returns(0) Puppet.features.stubs(:microsoft_windows?).returns(false) end it "should set euid/egid when root" do Process.stubs(:uid).returns(0) Process.stubs(:egid).returns(51) Process.stubs(:euid).returns(50) Puppet::Util::SUIDManager.stubs(:convert_xid).with(:gid, 51).returns(51) Puppet::Util::SUIDManager.stubs(:convert_xid).with(:uid, 50).returns(50) Puppet::Util::SUIDManager.stubs(:initgroups).returns([]) yielded = false Puppet::Util::SUIDManager.asuser(user[:uid], user[:gid]) do xids[:egid].should == user[:gid] xids[:euid].should == user[:uid] yielded = true end xids[:egid].should == 51 xids[:euid].should == 50 # It's possible asuser could simply not yield, so the assertions in the # block wouldn't fail. So verify those actually got checked. yielded.should be_true end it "should just yield if user and group are nil" do yielded = false Puppet::Util::SUIDManager.asuser(nil, nil) { yielded = true } yielded.should be_true xids.should == {} end it "should just change group if only group is given" do yielded = false Puppet::Util::SUIDManager.asuser(nil, 42) { yielded = true } yielded.should be_true xids.should == { :egid => 42 } end it "should change gid to the primary group of uid by default" do Process.stubs(:initgroups) yielded = false Puppet::Util::SUIDManager.asuser(42) { yielded = true } yielded.should be_true xids.should == { :euid => 42, :egid => 42 } end it "should change both uid and gid if given" do # I don't like the sequence, but it is the only way to assert on the # internal behaviour in a reliable fashion, given we need multiple # sequenced calls to the same methods. --daniel 2012-02-05 horror = sequence('of user and group changes') Puppet::Util::SUIDManager.expects(:change_group).with(43, false).in_sequence(horror) Puppet::Util::SUIDManager.expects(:change_user).with(42, false).in_sequence(horror) Puppet::Util::SUIDManager.expects(:change_group). with(Puppet::Util::SUIDManager.egid, false).in_sequence(horror) Puppet::Util::SUIDManager.expects(:change_user). with(Puppet::Util::SUIDManager.euid, false).in_sequence(horror) yielded = false Puppet::Util::SUIDManager.asuser(42, 43) { yielded = true } yielded.should be_true end end it "should not get or set euid/egid on Windows" do Puppet.features.stubs(:microsoft_windows?).returns true Puppet::Util::SUIDManager.asuser(user[:uid], user[:gid]) {} xids.should be_empty end end describe "#change_group" do describe "when changing permanently" do it "should change_privilege" do Process::GID.expects(:change_privilege).with do |gid| Process.gid = gid Process.egid = gid end Puppet::Util::SUIDManager.change_group(42, true) xids[:egid].should == 42 xids[:gid].should == 42 end end describe "when changing temporarily" do it "should change only egid" do Puppet::Util::SUIDManager.change_group(42, false) xids[:egid].should == 42 xids[:gid].should == 0 end end end describe "#change_user" do describe "when changing permanently" do it "should change_privilege" do Process::UID.expects(:change_privilege).with do |uid| Process.uid = uid Process.euid = uid end Puppet::Util::SUIDManager.expects(:initgroups).with(42) Puppet::Util::SUIDManager.change_user(42, true) xids[:euid].should == 42 xids[:uid].should == 42 end end describe "when changing temporarily" do it "should change only euid and groups" do Puppet::Util::SUIDManager.stubs(:initgroups).returns([]) Puppet::Util::SUIDManager.change_user(42, false) xids[:euid].should == 42 xids[:uid].should == 0 end it "should set euid before groups if changing to root" do Process.stubs(:euid).returns 50 when_not_root = sequence 'when_not_root' Process.expects(:euid=).in_sequence(when_not_root) Puppet::Util::SUIDManager.expects(:initgroups).in_sequence(when_not_root) Puppet::Util::SUIDManager.change_user(0, false) end it "should set groups before euid if changing from root" do Process.stubs(:euid).returns 0 when_root = sequence 'when_root' Puppet::Util::SUIDManager.expects(:initgroups).in_sequence(when_root) Process.expects(:euid=).in_sequence(when_root) Puppet::Util::SUIDManager.change_user(50, false) end end end describe "when running commands" do before :each do # We want to make sure $CHILD_STATUS is set Kernel.system '' if $CHILD_STATUS.nil? end describe "with #run_and_capture" do it "should capture the output and return process status" do Puppet::Util. expects(:execute). with('yay', :combine => true, :failonfail => false, :uid => user[:uid], :gid => user[:gid]). returns('output') output = Puppet::Util::SUIDManager.run_and_capture 'yay', user[:uid], user[:gid] output.first.should == 'output' output.last.should be_a(Process::Status) end end end describe "#root?" do describe "on POSIX systems" do before :each do Puppet.features.stubs(:posix?).returns(true) Puppet.features.stubs(:microsoft_windows?).returns(false) end it "should be root if uid is 0" do Process.stubs(:uid).returns(0) Puppet::Util::SUIDManager.should be_root end it "should not be root if uid is not 0" do Process.stubs(:uid).returns(1) Puppet::Util::SUIDManager.should_not be_root end end describe "on Microsoft Windows", :if => Puppet.features.microsoft_windows? do it "should be root if user is privileged" do Puppet::Util::Windows::User.stubs(:admin?).returns true Puppet::Util::SUIDManager.should be_root end it "should not be root if user is not privileged" do Puppet::Util::Windows::User.stubs(:admin?).returns false Puppet::Util::SUIDManager.should_not be_root end end end end describe 'Puppet::Util::SUIDManager#groups=' do subject do Puppet::Util::SUIDManager end it "(#3419) should rescue Errno::EINVAL on OS X" do Process.expects(:groups=).raises(Errno::EINVAL, 'blew up') subject.expects(:osx_maj_ver).returns('10.7').twice subject.groups = ['list', 'of', 'groups'] end it "(#3419) should fail if an Errno::EINVAL is raised NOT on OS X" do Process.expects(:groups=).raises(Errno::EINVAL, 'blew up') subject.expects(:osx_maj_ver).returns(false) - expect { subject.groups = ['list', 'of', 'groups'] }.should raise_error(Errno::EINVAL) + expect { subject.groups = ['list', 'of', 'groups'] }.to raise_error(Errno::EINVAL) end end