diff options
| author | Daniel Pittman <daniel@puppetlabs.com> | 2011-04-07 17:58:07 -0700 |
|---|---|---|
| committer | Daniel Pittman <daniel@puppetlabs.com> | 2011-04-07 18:01:33 -0700 |
| commit | 0f24db1c3fd0aa6601ba032aedbe25be36010954 (patch) | |
| tree | 08af1180a4468145af66ee429ce3a80236e08fff /spec | |
| parent | af792351a62399f8bbeba0d2425f2b88eabb3dff (diff) | |
| parent | 79f4774182046d2fdf392c1eb27ee78505659199 (diff) | |
| download | puppet-0f24db1c3fd0aa6601ba032aedbe25be36010954.tar.gz puppet-0f24db1c3fd0aa6601ba032aedbe25be36010954.tar.xz puppet-0f24db1c3fd0aa6601ba032aedbe25be36010954.zip | |
Merge puppet-interfaces into puppet.
This joins the two repositories, including full history, into a single run, as
well as landing the interfaces work on the next branch ready for release.
Diffstat (limited to 'spec')
32 files changed, 1509 insertions, 0 deletions
diff --git a/spec/lib/puppet/faces/huzzah.rb b/spec/lib/puppet/faces/huzzah.rb new file mode 100644 index 000000000..735004475 --- /dev/null +++ b/spec/lib/puppet/faces/huzzah.rb @@ -0,0 +1,4 @@ +require 'puppet/faces' +Puppet::Faces.define(:huzzah, '2.0.1') do + action :bar do "is where beer comes from" end +end diff --git a/spec/shared_behaviours/things_that_declare_options.rb b/spec/shared_behaviours/things_that_declare_options.rb new file mode 100644 index 000000000..1b41c2279 --- /dev/null +++ b/spec/shared_behaviours/things_that_declare_options.rb @@ -0,0 +1,134 @@ +# -*- coding: utf-8 -*- +shared_examples_for "things that declare options" do + it "should support options without arguments" do + subject = add_options_to { option "--bar" } + subject.should be_option :bar + end + + it "should support options with an empty block" do + subject = add_options_to do + option "--foo" do + # this section deliberately left blank + end + end + subject.should be + subject.should be_option :foo + end + + it "should support option documentation" do + text = "Sturm und Drang (German pronunciation: [ˈʃtʊʁm ʊnt ˈdʁaŋ]) …" + + subject = add_options_to do + option "--foo" do + desc text + end + end + + subject.get_option(:foo).desc.should == text + end + + it "should list all the options" do + subject = add_options_to do + option "--foo" + option "--bar" + end + subject.options.should =~ [:foo, :bar] + 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 + 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/ + 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/ + 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/ + 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/ + end + + it "should accept optional arguments" do + subject = add_options_to do option "--foo=[baz]", "--bar=[baz]" end + [:foo, :bar].each do |name| + subject.should be_option name + end + end + + describe "#takes_argument?" do + it "should detect an argument being absent" do + subject = add_options_to do option "--foo" end + subject.get_option(:foo).should_not be_takes_argument + end + ["=FOO", " FOO", "=[FOO]", " [FOO]"].each do |input| + it "should detect an argument given #{input.inspect}" do + subject = add_options_to do option "--foo#{input}" end + subject.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 be true if the argument is optional (like #{input.inspect})" do + option = add_options_to do option "--foo#{input}" end.get_option(:foo) + option.should be_takes_argument + option.should be_optional_argument + end + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 13470428e..fc63c6d19 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -20,6 +20,8 @@ module PuppetSpec end require 'pathname' +require 'tmpdir' + require 'lib/puppet_spec/verbose' require 'lib/puppet_spec/files' require 'lib/puppet_spec/fixtures' @@ -66,4 +68,5 @@ RSpec.configure do |config| end end +# close of the "don't evaluate twice" mess. end diff --git a/spec/unit/application/certificate_spec.rb b/spec/unit/application/certificate_spec.rb new file mode 100755 index 000000000..6153d9538 --- /dev/null +++ b/spec/unit/application/certificate_spec.rb @@ -0,0 +1,17 @@ +require 'puppet/application/certificate' + +describe Puppet::Application::Certificate do + it "should have a 'ca-location' option" do + # REVISIT: This is delegated from the face, and we will have a test there, + # so is this actually a valuable test? --daniel 2011-04-07 + subject.command_line.stubs(:args).returns %w{list} + subject.preinit + subject.should respond_to(:handle_ca_location) + end + + it "should accept the ca-location option" do + subject.command_line.stubs(:args).returns %w{--ca-location local list} + subject.preinit and subject.parse_options and subject.setup + subject.arguments.should == [{ :ca_location => "local" }] + end +end diff --git a/spec/unit/application/config_spec.rb b/spec/unit/application/config_spec.rb new file mode 100755 index 000000000..066df6a51 --- /dev/null +++ b/spec/unit/application/config_spec.rb @@ -0,0 +1,10 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') +require 'puppet/application/config' + +describe Puppet::Application::Config do + it "should be a subclass of Puppet::Application::FacesBase" do + Puppet::Application::Config.superclass.should equal(Puppet::Application::FacesBase) + end +end diff --git a/spec/unit/application/configurer_spec.rb b/spec/unit/application/configurer_spec.rb new file mode 100755 index 000000000..621039bcc --- /dev/null +++ b/spec/unit/application/configurer_spec.rb @@ -0,0 +1,31 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') +require 'puppet/application/configurer' +require 'puppet/indirector/catalog/rest' +require 'puppet/indirector/report/rest' +require 'tempfile' + +describe "Puppet::Application::Configurer" do + it "should retrieve and apply a catalog and submit a report" do + dirname = Dir.mktmpdir("puppetdir") + Puppet[:vardir] = dirname + Puppet[:confdir] = dirname + Puppet[:certname] = "foo" + @catalog = Puppet::Resource::Catalog.new + @file = Puppet::Resource.new(:file, File.join(dirname, "tmp_dir_resource"), :parameters => {:ensure => :present}) + @catalog.add_resource(@file) + + @report = Puppet::Transaction::Report.new("apply") + Puppet::Transaction::Report.stubs(:new).returns(@report) + + Puppet::Resource::Catalog::Rest.any_instance.stubs(:find).returns(@catalog) + @report.expects(:save) + + Puppet::Util::Log.stubs(:newdestination) + + Puppet::Application::Configurer.new.run + + @report.status.should == "changed" + end +end diff --git a/spec/unit/application/faces_base_spec.rb b/spec/unit/application/faces_base_spec.rb new file mode 100755 index 000000000..6d8815f44 --- /dev/null +++ b/spec/unit/application/faces_base_spec.rb @@ -0,0 +1,185 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') +require 'puppet/application/faces_base' +require 'tmpdir' + +class Puppet::Application::FacesBase::Basetest < Puppet::Application::FacesBase +end + +describe Puppet::Application::FacesBase do + before :all do + @dir = Dir.mktmpdir + $LOAD_PATH.push(@dir) + FileUtils.mkdir_p(File.join @dir, 'puppet', 'faces') + File.open(File.join(@dir, 'puppet', 'faces', 'basetest.rb'), 'w') do |f| + f.puts "Puppet::Faces.define(:basetest, '0.0.1')" + end + + Puppet::Faces.define(:basetest, '0.0.1') do + option("--[no-]boolean") + option("--mandatory MANDATORY") + option("--optional [OPTIONAL]") + + action :foo do + option("--action") + when_invoked { |*args| args.length } + end + end + end + + after :all do + FileUtils.remove_entry_secure @dir + $LOAD_PATH.pop + end + + let :app do + app = Puppet::Application::FacesBase::Basetest.new + app.stubs(:exit) + app.stubs(:puts) + app.command_line.stubs(:subcommand_name).returns 'subcommand' + Puppet::Util::Log.stubs(:newdestination) + app + end + + describe "#find_global_settings_argument" do + it "should not match --ca to --ca-location" do + option = mock('ca option', :optparse_args => ["--ca"]) + Puppet.settings.expects(:each).yields(:ca, option) + + app.find_global_settings_argument("--ca-location").should be_nil + end + end + + describe "#preinit" do + before :each do + app.command_line.stubs(:args).returns %w{} + end + + describe "parsing the command line" do + context "with just an action" do + before :all do + app.command_line.stubs(:args).returns %w{foo} + app.preinit + end + + it "should set the faces based on the type" do + app.face.name.should == :basetest + end + + it "should set the format based on the faces default" do + app.format.should == :pson + end + + it "should find the action" do + app.action.should be + app.action.name.should == :foo + end + end + + it "should fail if no action is given" do + expect { app.preinit }. + should raise_error ArgumentError, /No action given/ + end + + it "should report a sensible error when options with = fail" do + app.command_line.stubs(:args).returns %w{--action=bar foo} + expect { app.preinit }. + should raise_error ArgumentError, /Unknown option "--action"/ + end + + it "should fail if an action option is before the action" do + app.command_line.stubs(:args).returns %w{--action foo} + expect { app.preinit }. + should raise_error ArgumentError, /Unknown option "--action"/ + end + + it "should fail if an unknown option is before the action" do + app.command_line.stubs(:args).returns %w{--bar foo} + expect { app.preinit }. + should raise_error ArgumentError, /Unknown option "--bar"/ + end + + it "should not fail if an unknown option is after the action" do + app.command_line.stubs(:args).returns %w{foo --bar} + app.preinit + app.action.name.should == :foo + app.face.should_not be_option :bar + app.action.should_not be_option :bar + end + + it "should accept --bar as an argument to a mandatory option after action" do + app.command_line.stubs(:args).returns %w{foo --mandatory --bar} + app.preinit and app.parse_options + app.action.name.should == :foo + app.options.should == { :mandatory => "--bar" } + end + + it "should accept --bar as an argument to a mandatory option before action" do + app.command_line.stubs(:args).returns %w{--mandatory --bar foo} + app.preinit and app.parse_options + app.action.name.should == :foo + app.options.should == { :mandatory => "--bar" } + end + + it "should not skip when --foo=bar is given" do + app.command_line.stubs(:args).returns %w{--mandatory=bar --bar foo} + expect { app.preinit }. + should raise_error ArgumentError, /Unknown option "--bar"/ + end + + { "boolean options before" => %w{--trace foo}, + "boolean options after" => %w{foo --trace} + }.each do |name, args| + it "should accept global boolean settings #{name} the action" do + app.command_line.stubs(:args).returns args + app.preinit && app.parse_options + Puppet[:trace].should be_true + end + end + + { "before" => %w{--syslogfacility user1 foo}, + " after" => %w{foo --syslogfacility user1} + }.each do |name, args| + it "should accept global settings with arguments #{name} the action" do + app.command_line.stubs(:args).returns args + app.preinit && app.parse_options + Puppet[:syslogfacility].should == "user1" + end + end + end + end + + describe "#setup" do + it "should remove the action name from the arguments" do + app.command_line.stubs(:args).returns %w{--mandatory --bar foo} + app.preinit and app.parse_options and app.setup + app.arguments.should == [{ :mandatory => "--bar" }] + end + + it "should pass positional arguments" do + app.command_line.stubs(:args).returns %w{--mandatory --bar foo bar baz quux} + app.preinit and app.parse_options and app.setup + app.arguments.should == ['bar', 'baz', 'quux', { :mandatory => "--bar" }] + end + end + + describe "#main" do + before do + app.face = Puppet::Faces[:basetest, '0.0.1'] + app.action = app.face.get_action(:foo) + app.format = :pson + app.arguments = ["myname", "myarg"] + end + + it "should send the specified verb and name to the faces" do + app.face.expects(:foo).with(*app.arguments) + app.main + end + + it "should use its render method to render any result" do + app.expects(:render).with(app.arguments.length + 1) + app.main + end + end +end diff --git a/spec/unit/application/faces_spec.rb b/spec/unit/application/faces_spec.rb new file mode 100755 index 000000000..d945c40b5 --- /dev/null +++ b/spec/unit/application/faces_spec.rb @@ -0,0 +1,10 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') +require 'puppet/application/faces' + +describe Puppet::Application::Faces do + it "should be an application" do + Puppet::Application::Faces.superclass.should equal(Puppet::Application) + end +end diff --git a/spec/unit/application/indirection_base_spec.rb b/spec/unit/application/indirection_base_spec.rb new file mode 100755 index 000000000..a73cf4fca --- /dev/null +++ b/spec/unit/application/indirection_base_spec.rb @@ -0,0 +1,39 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') +require 'puppet/application/indirection_base' +require 'puppet/faces/indirector' + +######################################################################## +# Stub for testing; the names are critical, sadly. --daniel 2011-03-30 +class Puppet::Application::TestIndirection < Puppet::Application::IndirectionBase +end + +face = Puppet::Faces::Indirector.define(:testindirection, '0.0.1') do +end +# REVISIT: This horror is required because we don't allow anything to be +# :current except for if it lives on, and is loaded from, disk. --daniel 2011-03-29 +face.version = :current +Puppet::Faces.register(face) +######################################################################## + + +describe Puppet::Application::IndirectionBase do + subject { Puppet::Application::TestIndirection.new } + + it "should accept a terminus command line option" do + # It would be nice not to have to stub this, but whatever... writing an + # entire indirection stack would cause us more grief. --daniel 2011-03-31 + terminus = mock("test indirection terminus") + Puppet::Indirector::Indirection.expects(:instance). + with(:testindirection).twice.returns() + + subject.command_line. + instance_variable_set('@args', %w{--terminus foo save}) + + # Not a very nice thing. :( + $stderr.stubs(:puts) + + expect { subject.run }.should raise_error SystemExit + end +end diff --git a/spec/unit/faces/catalog_spec.rb b/spec/unit/faces/catalog_spec.rb new file mode 100755 index 000000000..e0a771d10 --- /dev/null +++ b/spec/unit/faces/catalog_spec.rb @@ -0,0 +1,4 @@ +require 'puppet/faces' +describe Puppet::Faces[:catalog, '0.0.1'] do + it "should actually have some testing..." +end diff --git a/spec/unit/faces/certificate_request_spec.rb b/spec/unit/faces/certificate_request_spec.rb new file mode 100755 index 000000000..1a71a8379 --- /dev/null +++ b/spec/unit/faces/certificate_request_spec.rb @@ -0,0 +1,3 @@ +describe Puppet::Faces[:certificate_request, '0.0.1'] do + it "should actually have some tests..." +end diff --git a/spec/unit/faces/certificate_revocation_list_spec.rb b/spec/unit/faces/certificate_revocation_list_spec.rb new file mode 100755 index 000000000..4f41edef6 --- /dev/null +++ b/spec/unit/faces/certificate_revocation_list_spec.rb @@ -0,0 +1,3 @@ +describe Puppet::Faces[:certificate_revocation_list, '0.0.1'] do + it "should actually have some tests..." +end diff --git a/spec/unit/faces/certificate_spec.rb b/spec/unit/faces/certificate_spec.rb new file mode 100755 index 000000000..ba264f967 --- /dev/null +++ b/spec/unit/faces/certificate_spec.rb @@ -0,0 +1,14 @@ +require 'puppet/ssl/host' + +describe Puppet::Faces[:certificate, '0.0.1'] do + it "should have a ca-location option" do + subject.should be_option :ca_location + end + + it "should set the ca location when invoked" do + pending "#6983: This is broken in the actual faces..." + Puppet::SSL::Host.expects(:ca_location=).with(:foo) + Puppet::SSL::Host.indirection.expects(:save) + subject.sign :ca_location => :foo + end +end diff --git a/spec/unit/faces/config_spec.rb b/spec/unit/faces/config_spec.rb new file mode 100755 index 000000000..2eb04a81b --- /dev/null +++ b/spec/unit/faces/config_spec.rb @@ -0,0 +1,24 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') + +describe Puppet::Faces[:config, '0.0.1'] do + it "should use Settings#print_config_options when asked to print" do + Puppet.settings.stubs(:puts) + Puppet.settings.expects(:print_config_options) + subject.print + end + + it "should set 'configprint' to all desired values and call print_config_options when a specific value is provided" do + Puppet.settings.stubs(:puts) + Puppet.settings.expects(:print_config_options) + subject.print("libdir", "ssldir") + Puppet.settings[:configprint].should == "libdir,ssldir" + end + + it "should always return nil" do + Puppet.settings.stubs(:puts) + Puppet.settings.expects(:print_config_options) + subject.print("libdir").should be_nil + end +end diff --git a/spec/unit/faces/configurer_spec.rb b/spec/unit/faces/configurer_spec.rb new file mode 100755 index 000000000..270888ca1 --- /dev/null +++ b/spec/unit/faces/configurer_spec.rb @@ -0,0 +1,24 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') +require 'puppet/indirector/catalog/rest' +require 'tempfile' + +describe Puppet::Faces[:configurer, '0.0.1'] do + describe "#synchronize" do + it "should retrieve and apply a catalog and return a report" do + dirname = Dir.mktmpdir("puppetdir") + Puppet[:vardir] = dirname + Puppet[:confdir] = dirname + @catalog = Puppet::Resource::Catalog.new + @file = Puppet::Resource.new(:file, File.join(dirname, "tmp_dir_resource"), :parameters => {:ensure => :present}) + @catalog.add_resource(@file) + Puppet::Resource::Catalog::Rest.any_instance.stubs(:find).returns(@catalog) + + report = subject.synchronize("foo") + + report.kind.should == "apply" + report.status.should == "changed" + end + end +end diff --git a/spec/unit/faces/facts_spec.rb b/spec/unit/faces/facts_spec.rb new file mode 100755 index 000000000..480f463e4 --- /dev/null +++ b/spec/unit/faces/facts_spec.rb @@ -0,0 +1,21 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') + +describe Puppet::Faces[:facts, '0.0.1'] do + it "should define an 'upload' fact" do + subject.should be_action(:upload) + end + + it "should set its default format to :yaml" do + subject.default_format.should == :yaml + end + + describe "when uploading" do + it "should set the terminus_class to :facter" + + it "should set the cach_eclass to :rest" + + it "should find the current certname" + end +end diff --git a/spec/unit/faces/file_spec.rb b/spec/unit/faces/file_spec.rb new file mode 100755 index 000000000..fcb52c67e --- /dev/null +++ b/spec/unit/faces/file_spec.rb @@ -0,0 +1,3 @@ +describe Puppet::Faces[:file, '0.0.1'] do + it "should actually have some tests..." +end diff --git a/spec/unit/faces/indirector_spec.rb b/spec/unit/faces/indirector_spec.rb new file mode 100755 index 000000000..3ed64bc01 --- /dev/null +++ b/spec/unit/faces/indirector_spec.rb @@ -0,0 +1,60 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') +require 'puppet/faces/indirector' + +describe Puppet::Faces::Indirector do + subject do + instance = Puppet::Faces::Indirector.new(:test, '0.0.1') + indirection = stub('indirection', + :name => :stub_indirection, + :reset_terminus_class => nil) + instance.stubs(:indirection).returns indirection + instance + end + + it "should be able to return a list of indirections" do + Puppet::Faces::Indirector.indirections.should be_include("catalog") + end + + it "should be able to return a list of terminuses for a given indirection" do + Puppet::Faces::Indirector.terminus_classes(:catalog).should be_include("compiler") + end + + describe "as an instance" do + it "should be able to determine its indirection" do + # Loading actions here an get, um, complicated + Puppet::Faces.stubs(:load_actions) + Puppet::Faces::Indirector.new(:catalog, '0.0.1').indirection.should equal(Puppet::Resource::Catalog.indirection) + end + end + + [:find, :search, :save, :destroy].each do |method| + it "should define a '#{method}' action" do + Puppet::Faces::Indirector.should be_action(method) + end + + it "should call the indirection method with options when the '#{method}' action is invoked" do + subject.indirection.expects(method).with(:test, "myargs", {}) + subject.send(method, :test, "myargs") + end + it "should forward passed options" do + subject.indirection.expects(method).with(:test, "action", {'one'=>'1'}) + subject.send(method, :test, 'action', {'one'=>'1'}) + end + end + + it "should be able to override its indirection name" do + subject.set_indirection_name :foo + subject.indirection_name.should == :foo + end + + it "should be able to set its terminus class" do + subject.indirection.expects(:terminus_class=).with(:myterm) + subject.set_terminus(:myterm) + end + + it "should define a class-level 'info' action" do + Puppet::Faces::Indirector.should be_action(:info) + end +end diff --git a/spec/unit/faces/key_spec.rb b/spec/unit/faces/key_spec.rb new file mode 100755 index 000000000..9b7a58706 --- /dev/null +++ b/spec/unit/faces/key_spec.rb @@ -0,0 +1,3 @@ +describe Puppet::Faces[:key, '0.0.1'] do + it "should actually have some tests..." +end diff --git a/spec/unit/faces/node_spec.rb b/spec/unit/faces/node_spec.rb new file mode 100755 index 000000000..4639bdf63 --- /dev/null +++ b/spec/unit/faces/node_spec.rb @@ -0,0 +1,9 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') + +describe Puppet::Faces[:node, '0.0.1'] do + it "should set its default format to :yaml" do + subject.default_format.should == :yaml + end +end diff --git a/spec/unit/faces/report_spec.rb b/spec/unit/faces/report_spec.rb new file mode 100755 index 000000000..30897d5e7 --- /dev/null +++ b/spec/unit/faces/report_spec.rb @@ -0,0 +1,3 @@ +describe Puppet::Faces[:report, '0.0.1'] do + it "should actually have some tests..." +end diff --git a/spec/unit/faces/resource_spec.rb b/spec/unit/faces/resource_spec.rb new file mode 100755 index 000000000..e3f2e1c62 --- /dev/null +++ b/spec/unit/faces/resource_spec.rb @@ -0,0 +1,3 @@ +describe Puppet::Faces[:resource, '0.0.1'] do + it "should actually have some tests..." +end diff --git a/spec/unit/faces/resource_type_spec.rb b/spec/unit/faces/resource_type_spec.rb new file mode 100755 index 000000000..fcbf07520 --- /dev/null +++ b/spec/unit/faces/resource_type_spec.rb @@ -0,0 +1,3 @@ +describe Puppet::Faces[:resource_type, '0.0.1'] do + it "should actually have some tests..." +end diff --git a/spec/unit/interface/action_builder_spec.rb b/spec/unit/interface/action_builder_spec.rb new file mode 100755 index 000000000..ae9cc83d4 --- /dev/null +++ b/spec/unit/interface/action_builder_spec.rb @@ -0,0 +1,59 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') +require 'puppet/interface/action_builder' + +describe Puppet::Interface::ActionBuilder do + describe "::build" do + it "should build an action" do + action = Puppet::Interface::ActionBuilder.build(nil, :foo) do + 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') + action = Puppet::Interface::ActionBuilder.build(face, :foo) do + when_invoked do + "invoked the method" + end + 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") + end + + describe "when handling options" do + let :face do Puppet::Interface.new(:option_handling, '0.0.1') end + + it "should have a #option DSL function" do + method = nil + Puppet::Interface::ActionBuilder.build(face, :foo) do + method = self.method(:option) + end + method.should be + end + + it "should define an option without a block" do + action = Puppet::Interface::ActionBuilder.build(face, :foo) do + option "--bar" + end + action.should be_option :bar + end + + it "should accept an empty block" do + action = Puppet::Interface::ActionBuilder.build(face, :foo) do + option "--bar" do + # This space left deliberately blank. + end + end + action.should be_option :bar + end + end + end +end diff --git a/spec/unit/interface/action_manager_spec.rb b/spec/unit/interface/action_manager_spec.rb new file mode 100755 index 000000000..50bea5f89 --- /dev/null +++ b/spec/unit/interface/action_manager_spec.rb @@ -0,0 +1,233 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') + +# This is entirely an internal class for Interface, so we have to load it instead of our class. +require 'puppet/interface' + +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 { "something "} + end + end + + it "should be able to define a 'script' style action" do + subject.script :bar do + "a bar is where beer is found" + end + end + + it "should be able to list defined actions" do + subject.action(:foo) do + when_invoked { "something" } + end + subject.action(:bar) do + when_invoked { "something" } + end + + subject.actions.should =~ [:foo, :bar] + end + + it "should list 'script' actions" do + subject.script :foo do "foo" end + subject.actions.should =~ [:foo] + end + + it "should list both script and normal actions" do + subject.action :foo do + when_invoked do "foo" end + end + subject.script :bar do "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 { "something" } + end + + subject.should be_action(:foo) + end + + it "should indicate an action is defined for script actions" do + subject.script :foo do "foo" end + subject.should be_action :foo + end + + it "should correctly treat action names specified as strings" do + subject.action(:foo) do + when_invoked { "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 { "something "} + end + end + + it "should be able to list defined actions" do + subject.action(:foo) do + when_invoked { "something" } + end + subject.action(:bar) do + when_invoked { "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) { "something" } + 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 + end + @instance = @klass.new + end + + it "should be able to define an action at the class level" do + @klass.action(:foo) do + when_invoked { "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 { "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 { "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 { "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 { "something" } + end + @klass.action(:bar) do + when_invoked { "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 { "something" } + end + @instance.action(:bar) do + when_invoked { "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 { "something" } + end + @instance.action(:bar) do + when_invoked { "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 { "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 { "something" } + end + @instance.should be_action(:foo) + end + + it "should list actions defined in superclasses" do + @subclass = Class.new(@klass) + @instance = @subclass.new + + @klass.action(:parent) do + when_invoked { "a" } + end + @subclass.action(:sub) do + when_invoked { "a" } + end + @instance.action(:instance) do + when_invoked { "a" } + end + + @instance.should be_action(:parent) + @instance.should be_action(:sub) + @instance.should be_action(:instance) + 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 { "something" } + end + @instance.foo.should == "something" + end + end + + describe "#get_action" do + let :parent_class do + parent_class = Class.new(Puppet::Interface) + parent_class.action(:foo) {} + 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 new file mode 100755 index 000000000..4801a3cc8 --- /dev/null +++ b/spec/unit/interface/action_spec.rb @@ -0,0 +1,173 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') +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/) + end + 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 { 25 } + end + + action(:bar) do + when_invoked { "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 { 25 } + end + + action(:bar) do + when_invoked { "the value of foo is '#{foo}'" } + end + + action(:quux) do + when_invoked { "qux told me #{qux}" } + end + end + + face = Puppet::Interface::MyInterfaceBaseClass.new(:my_inherited_face, '0.0.1') do + action(:baz) do + when_invoked { "the value of foo in baz is '#{foo}'" } + end + + action(:qux) do + when_invoked { 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 + when_invoked do |options| + options + 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 + 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 + 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 option "--bar" end + end + + face.get_action(:foo).options.should =~ [:bar] + 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 option "--bar" end + action :baz do 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 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, &block) + 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 + end + end +end diff --git a/spec/unit/interface/face_collection_spec.rb b/spec/unit/interface/face_collection_spec.rb new file mode 100755 index 000000000..bf3801efd --- /dev/null +++ b/spec/unit/interface/face_collection_spec.rb @@ -0,0 +1,175 @@ +#!/usr/bin/env ruby +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') + +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 + before :each do + @original_faces = subject.instance_variable_get("@faces").dup + @original_required = $".dup + subject.instance_variable_get("@faces").clear + end + + after :each do + subject.instance_variable_set("@faces", @original_faces) + $".clear ; @original_required.each do |item| $" << item end + end + + describe "::faces" do + it "REVISIT: should have some tests here, if we describe it" + end + + describe "::validate_version" do + it 'should permit three number versions' do + subject.validate_version('10.10.10').should == true + end + + it 'should permit versions with appended descriptions' do + subject.validate_version('10.10.10beta').should == true + end + + it 'should not permit versions with more than three numbers' do + subject.validate_version('1.2.3.4').should == false + end + + it 'should not permit versions with only two numbers' do + subject.validate_version('10.10').should == false + end + + it 'should not permit versions with only one number' do + subject.validate_version('123').should == false + end + + it 'should not permit versions with text in any position but at the end' do + subject.validate_version('v1.1.1').should == false + end + end + + describe "::[]" do + before :each do + subject.instance_variable_get("@faces")[:foo]['0.0.1'] = 10 + end + + before :each do + @dir = Dir.mktmpdir + @lib = FileUtils.mkdir_p(File.join @dir, 'puppet', 'faces') + $LOAD_PATH.push(@dir) + end + + after :each do + FileUtils.remove_entry_secure @dir + $LOAD_PATH.pop + end + + it "should return the faces with the given name" do + subject["foo", '0.0.1'].should == 10 + end + + it "should attempt to load the faces if it isn't found" do + subject.expects(:require).with('puppet/faces/bar') + subject["bar", '0.0.1'] + end + + it "should attempt to load the default faces for the specified version :current" do + subject.expects(:require).never # except... + subject.expects(:require).with('puppet/faces/fozzie') + subject['fozzie', :current] + end + end + + describe "::face?" do + before :each do + subject.instance_variable_get("@faces")[:foo]['0.0.1'] = 10 + end + + it "should return true if the faces specified is registered" do + subject.face?("foo", '0.0.1').should == true + end + + it "should attempt to require the faces if it is not registered" do + subject.expects(:require).with do |file| + subject.instance_variable_get("@faces")[:bar]['0.0.1'] = true + file == 'puppet/faces/bar' + end + subject.face?("bar", '0.0.1').should == true + end + + it "should return true if requiring the faces registered it" do + subject.stubs(:require).with do + subject.instance_variable_get("@faces")[:bar]['0.0.1'] = 20 + end + end + + it "should return false if the faces is not registered" do + subject.stubs(:require).returns(true) + subject.face?("bar", '0.0.1').should be_false + end + + it "should return false if the faces file itself is missing" do + subject.stubs(:require). + raises(LoadError, 'no such file to load -- puppet/faces/bar') + subject.face?("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_faces + file == 'puppet/faces/huzzah' + end + subject.face?("huzzah", :current) + subject.instance_variable_get("@faces")[:huzzah][:current].should == :huzzah_faces + end + + context "with something on disk" do + it "should register the version loaded from `puppet/faces/{name}` as `:current`" do + subject.should be_face "huzzah", '2.0.1' + subject.should be_face "huzzah", :current + Puppet::Faces[:huzzah, '2.0.1'].should == Puppet::Faces[: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/faces/huzzah' + subject.face?(:huzzah, :current).should be_true + end + end + end + + describe "::register" do + it "should store the faces by name" do + faces = Puppet::Faces.new(:my_faces, '0.0.1') + subject.register(faces) + subject.instance_variable_get("@faces").should == {:my_faces => {'0.0.1' => faces}} + 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/ + end + end + end +end diff --git a/spec/unit/interface/option_builder_spec.rb b/spec/unit/interface/option_builder_spec.rb new file mode 100644 index 000000000..fae48324e --- /dev/null +++ b/spec/unit/interface/option_builder_spec.rb @@ -0,0 +1,29 @@ +require 'puppet/interface/option_builder' + +describe Puppet::Interface::OptionBuilder do + let :face do Puppet::Interface.new(:option_builder_testing, '0.0.1') end + + it "should be able to construct an option without a block" do + Puppet::Interface::OptionBuilder.build(face, "--foo"). + should be_an_instance_of Puppet::Interface::Option + end + + describe "when using the DSL block" do + it "should work with an empty block" do + option = Puppet::Interface::OptionBuilder.build(face, "--foo") do + # This block deliberately left blank. + end + + option.should be_an_instance_of Puppet::Interface::Option + end + + it "should support documentation declarations" do + text = "this is the description" + option = Puppet::Interface::OptionBuilder.build(face, "--foo") do + desc text + end + option.should be_an_instance_of Puppet::Interface::Option + option.desc.should == text + end + end +end diff --git a/spec/unit/interface/option_spec.rb b/spec/unit/interface/option_spec.rb new file mode 100644 index 000000000..3bcd121e2 --- /dev/null +++ b/spec/unit/interface/option_spec.rb @@ -0,0 +1,75 @@ +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/ + 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 \(--\)/ + end + end + end + + it "requires a face when created" do + expect { Puppet::Interface::Option.new }. + should 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/ + 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 +end diff --git a/spec/unit/interface_spec.rb b/spec/unit/interface_spec.rb new file mode 100755 index 000000000..ea11b21ba --- /dev/null +++ b/spec/unit/interface_spec.rb @@ -0,0 +1,147 @@ +require 'puppet/faces' +require 'puppet/interface' + +describe Puppet::Interface do + subject { Puppet::Interface } + + before :all do + @faces = Puppet::Interface::FaceCollection.instance_variable_get("@faces").dup + end + + before :each do + Puppet::Interface::FaceCollection.instance_variable_get("@faces").clear + end + + after :all do + Puppet::Interface::FaceCollection.instance_variable_set("@faces", @faces) + 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) }.should raise_error ArgumentError + end + end + + describe "#initialize" do + it "should require a version number" do + expect { subject.new(:no_version) }.should raise_error ArgumentError + end + + it "should require a valid version number" do + expect { subject.new(:bad_version, 'Rasins') }. + should raise_error ArgumentError + end + + it "should instance-eval any provided block" 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 + + it "should allow overriding of the default format" do + face = subject.new(:me, '0.0.1') + face.set_default_format :foo + face.default_format.should == :foo + end + + it "should default to :pson for its format" do + subject.new(:me, '0.0.1').default_format.should == :pson + 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 + pending "mocking require causes random stack overflow" + subject::FaceCollection.expects(:require).with "puppet/faces/foo" + subject[:foo, '0.0.1'] + end + + it "should be able to load all actions in all search paths" + + + 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" 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 + 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 option "--foo" end + option "--foo" + end + }.should 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 option "--quux" end + action :bar do option "--quux" end + end + + face.get_action(:foo).options.should =~ [:quux] + face.get_action(:bar).options.should =~ [:quux] + end + end + + describe "with inherited options" do + let :face do + parent = Class.new(subject) + parent.option("--inherited") + face = parent.new(:example, '0.2.1') + face.option("--local") + face + end + + describe "#options" do + it "should list inherited options" do + face.options.should =~ [:inherited, :local] + end + end + + describe "#get_option" do + it "should return an inherited option object" do + face.get_option(:inherited).should be_an_instance_of subject::Option + end + end + end +end diff --git a/spec/unit/puppet/provider/README.markdown b/spec/unit/puppet/provider/README.markdown new file mode 100644 index 000000000..702585021 --- /dev/null +++ b/spec/unit/puppet/provider/README.markdown @@ -0,0 +1,4 @@ +Provider Specs +============== + +Define specs for your providers under this directory. diff --git a/spec/unit/puppet/type/README.markdown b/spec/unit/puppet/type/README.markdown new file mode 100644 index 000000000..1ee19ac84 --- /dev/null +++ b/spec/unit/puppet/type/README.markdown @@ -0,0 +1,4 @@ +Resource Type Specs +=================== + +Define specs for your resource types in this directory. |
