diff --git a/spec/integration/application/apply_spec.rb b/spec/integration/application/apply_spec.rb index 3ce1277d3..4d9552f99 100755 --- a/spec/integration/application/apply_spec.rb +++ b/spec/integration/application/apply_spec.rb @@ -1,115 +1,115 @@ require 'spec_helper' require 'puppet_spec/files' describe "apply" do include PuppetSpec::Files before :each do Puppet[:reports] = "none" end describe "when applying provided catalogs" do it "can apply catalogs provided in a file in pson" do file_to_create = tmpfile("pson_catalog") catalog = Puppet::Resource::Catalog.new('mine', Puppet.lookup(:environments).get(Puppet[:environment])) resource = Puppet::Resource.new(:file, file_to_create, :parameters => {:content => "my stuff"}) catalog.add_resource resource manifest = file_containing("manifest", catalog.to_pson) puppet = Puppet::Application[:apply] puppet.options[:catalog] = manifest puppet.apply - expect(Puppet::FileSystem.exist?(file_to_create)).to be_true + expect(Puppet::FileSystem.exist?(file_to_create)).to be_truthy expect(File.read(file_to_create)).to eq("my stuff") end end it "applies a given file even when a directory environment is specified" do manifest = file_containing("manifest.pp", "notice('it was applied')") special = Puppet::Node::Environment.create(:special, []) Puppet.override(:current_environment => special) do Puppet[:environment] = 'special' puppet = Puppet::Application[:apply] puppet.stubs(:command_line).returns(stub('command_line', :args => [manifest])) expect { puppet.run_command }.to exit_with(0) end expect(@logs.map(&:to_s)).to include('it was applied') end it "applies a given file even when an ENC is configured", :if => !Puppet.features.microsoft_windows? do manifest = file_containing("manifest.pp", "notice('specific manifest applied')") site_manifest = file_containing("site_manifest.pp", "notice('the site manifest was applied instead')") enc = file_containing("enc_script", "#!/bin/sh\necho 'classes: []'") File.chmod(0755, enc) special = Puppet::Node::Environment.create(:special, []) Puppet.override(:current_environment => special) do Puppet[:environment] = 'special' Puppet[:node_terminus] = 'exec' Puppet[:external_nodes] = enc Puppet[:manifest] = site_manifest puppet = Puppet::Application[:apply] puppet.stubs(:command_line).returns(stub('command_line', :args => [manifest])) expect { puppet.run_command }.to exit_with(0) end expect(@logs.map(&:to_s)).to include('specific manifest applied') end context "with a module" do let(:modulepath) { tmpdir('modulepath') } let(:execute) { 'include amod' } let(:args) { ['-e', execute, '--modulepath', modulepath] } before(:each) do dir_contained_in(modulepath, { "amod" => { "manifests" => { "init.pp" => "class amod{ notice('amod class included') }" } } }) Puppet[:environmentpath] = dir_containing("environments", { Puppet[:environment] => {} }) end def init_cli_args_and_apply_app(args, execute) Puppet.initialize_settings(args) puppet = Puppet::Application.find(:apply).new(stub('command_line', :subcommand_name => :apply, :args => args)) puppet.options[:code] = execute return puppet end it "looks in --modulepath even when the default directory environment exists" do apply = init_cli_args_and_apply_app(args, execute) expect do expect { apply.run }.to exit_with(0) end.to have_printed('amod class included') end it "looks in --modulepath even when given a specific directory --environment" do args << '--environment' << 'production' apply = init_cli_args_and_apply_app(args, execute) expect do expect { apply.run }.to exit_with(0) end.to have_printed('amod class included') end it "looks in --modulepath when given multiple paths in --modulepath" do args = ['-e', execute, '--modulepath', [tmpdir('notmodulepath'), modulepath].join(File::PATH_SEPARATOR)] apply = init_cli_args_and_apply_app(args, execute) expect do expect { apply.run }.to exit_with(0) end.to have_printed('amod class included') end end end diff --git a/spec/integration/defaults_spec.rb b/spec/integration/defaults_spec.rb index 8cd2b5796..c2cae2989 100755 --- a/spec/integration/defaults_spec.rb +++ b/spec/integration/defaults_spec.rb @@ -1,234 +1,234 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/defaults' describe "Puppet defaults" do describe "when default_manifest is set" do it "returns ./manifests by default" do expect(Puppet[:default_manifest]).to eq('./manifests') end end describe "when disable_per_environment_manifest is set" do it "returns false by default" do expect(Puppet[:disable_per_environment_manifest]).to eq(false) end it "errors when set to true and default_manifest is not an absolute path" do expect { Puppet[:default_manifest] = './some/relative/manifest.pp' Puppet[:disable_per_environment_manifest] = true }.to raise_error Puppet::Settings::ValidationError, /'default_manifest' setting must be.*absolute/ end end 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 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 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.stubs(:service_group_available?).returns true Puppet.settings.setting(:yamldir).owner.should == Puppet.settings[:user] Puppet.settings.setting(:yamldir).group.should == Puppet.settings[:group] 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 [: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::Settings::PathSetting) end end describe "on a Unix-like platform it", :if => Puppet.features.posix? do it "should add /usr/sbin and /sbin to the path if they're not there" do Puppet::Util.withenv("PATH" => "/usr/bin#{File::PATH_SEPARATOR}/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 end describe "on a Windows-like platform it", :if => Puppet.features.microsoft_windows? do it "should not add anything" do path = "c:\\windows\\system32#{File::PATH_SEPARATOR}c:\\windows" Puppet::Util.withenv("PATH" => path) do Puppet.settings[:path] = "none" # this causes it to ignore the setting ENV["PATH"].should == path end end end it "should default to pson for the preferred serialization format" do Puppet.settings.value(:preferred_serialization_format).should == "pson" 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 use report_port when set" do Puppet.settings[:masterport] = "1234" Puppet.settings[:report_port] = "5678" Puppet.settings[:report_port].should == "5678" 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 + Puppet.settings[:certificate_revocation].should be_truthy 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 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 describe "when configuring hiera" do it "should have a hiera_config setting" do Puppet.settings[:hiera_config].should_not be_nil end end describe "when configuring the data_binding terminus" do it "should have a data_binding_terminus setting" do Puppet.settings[:data_binding_terminus].should_not be_nil end it "should be set to hiera by default" do Puppet.settings[:data_binding_terminus].should == :hiera end end describe "agent_catalog_run_lockfile" do it "(#2888) is not a file setting so it is absent from the Settings catalog" do Puppet.settings.setting(:agent_catalog_run_lockfile).should_not be_a_kind_of Puppet::Settings::FileSetting Puppet.settings.setting(:agent_catalog_run_lockfile).should be_a Puppet::Settings::StringSetting end end end diff --git a/spec/integration/parser/compiler_spec.rb b/spec/integration/parser/compiler_spec.rb index 176ecb0a2..5ca44de46 100755 --- a/spec/integration/parser/compiler_spec.rb +++ b/spec/integration/parser/compiler_spec.rb @@ -1,927 +1,927 @@ require 'spec_helper' require 'puppet_spec/compiler' require 'matchers/resource' class CompilerTestResource attr_accessor :builtin, :virtual, :evaluated, :type, :title def initialize(type, title) @type = type @title = title end def [](attr) return nil if attr == :stage :main end def ref "#{type.to_s.capitalize}[#{title}]" end def evaluated? @evaluated end def builtin_type? @builtin end def virtual? @virtual end def class? false end def stage? false end def evaluate end def file "/fake/file/goes/here" end def line "42" end end describe Puppet::Parser::Compiler do include PuppetSpec::Files include Matchers::Resource def resource(type, title) Puppet::Parser::Resource.new(type, title, :scope => @scope) end let(:environment) { Puppet::Node::Environment.create(:testing, []) } before :each do # Push me faster, I wanna go back in time! (Specifically, freeze time # across the test since we have a bunch of version == timestamp code # hidden away in the implementation and we keep losing the race.) # --daniel 2011-04-21 now = Time.now Time.stubs(:now).returns(now) @node = Puppet::Node.new("testnode", :facts => Puppet::Node::Facts.new("facts", {}), :environment => environment) @known_resource_types = environment.known_resource_types @compiler = Puppet::Parser::Compiler.new(@node) @scope = Puppet::Parser::Scope.new(@compiler, :source => stub('source')) @scope_resource = Puppet::Parser::Resource.new(:file, "/my/file", :scope => @scope) @scope.resource = @scope_resource end it "should fail intelligently when a class-level compile fails" do Puppet::Parser::Compiler.expects(:new).raises ArgumentError lambda { Puppet::Parser::Compiler.compile(@node) }.should raise_error(Puppet::Error) end it "should use the node's environment as its environment" do @compiler.environment.should equal(@node.environment) end it "fails if the node's environment has validation errors" do conflicted_environment = Puppet::Node::Environment.create(:testing, [], '/some/environment.conf/manifest.pp') conflicted_environment.stubs(:validation_errors).returns(['bad environment']) @node.environment = conflicted_environment expect { Puppet::Parser::Compiler.compile(@node) }.to raise_error(Puppet::Error, /Compilation has been halted because.*bad environment/) end it "should include the resource type collection helper" do Puppet::Parser::Compiler.ancestors.should be_include(Puppet::Resource::TypeCollectionHelper) end it "should be able to return a class list containing all added classes" do @compiler.add_class "" @compiler.add_class "one" @compiler.add_class "two" @compiler.classlist.sort.should == %w{one two}.sort end describe "when initializing" do it "should set its node attribute" do @compiler.node.should equal(@node) end it "should detect when ast nodes are absent" do - @compiler.ast_nodes?.should be_false + @compiler.ast_nodes?.should be_falsey end it "should detect when ast nodes are present" do @known_resource_types.expects(:nodes?).returns true - @compiler.ast_nodes?.should be_true + @compiler.ast_nodes?.should be_truthy end it "should copy the known_resource_types version to the catalog" do @compiler.catalog.version.should == @known_resource_types.version end it "should copy any node classes into the class list" do node = Puppet::Node.new("mynode") node.classes = %w{foo bar} compiler = Puppet::Parser::Compiler.new(node) compiler.classlist.should =~ ['foo', 'bar'] end it "should transform node class hashes into a class list" do node = Puppet::Node.new("mynode") node.classes = {'foo'=>{'one'=>'p1'}, 'bar'=>{'two'=>'p2'}} compiler = Puppet::Parser::Compiler.new(node) compiler.classlist.should =~ ['foo', 'bar'] end it "should add a 'main' stage to the catalog" do @compiler.catalog.resource(:stage, :main).should be_instance_of(Puppet::Parser::Resource) end end describe "when managing scopes" do it "should create a top scope" do @compiler.topscope.should be_instance_of(Puppet::Parser::Scope) end it "should be able to create new scopes" do @compiler.newscope(@compiler.topscope).should be_instance_of(Puppet::Parser::Scope) end it "should set the parent scope of the new scope to be the passed-in parent" do scope = mock 'scope' newscope = @compiler.newscope(scope) newscope.parent.should equal(scope) end it "should set the parent scope of the new scope to its topscope if the parent passed in is nil" do scope = mock 'scope' newscope = @compiler.newscope(nil) newscope.parent.should equal(@compiler.topscope) end end describe "when compiling" do def compile_methods [:set_node_parameters, :evaluate_main, :evaluate_ast_node, :evaluate_node_classes, :evaluate_generators, :fail_on_unevaluated, :finish, :store, :extract, :evaluate_relationships] end # Stub all of the main compile methods except the ones we're specifically interested in. def compile_stub(*except) (compile_methods - except).each { |m| @compiler.stubs(m) } end it "should set node parameters as variables in the top scope" do params = {"a" => "b", "c" => "d"} @node.stubs(:parameters).returns(params) compile_stub(:set_node_parameters) @compiler.compile @compiler.topscope['a'].should == "b" @compiler.topscope['c'].should == "d" end it "should set the client and server versions on the catalog" do params = {"clientversion" => "2", "serverversion" => "3"} @node.stubs(:parameters).returns(params) compile_stub(:set_node_parameters) @compiler.compile @compiler.catalog.client_version.should == "2" @compiler.catalog.server_version.should == "3" end it "should evaluate the main class if it exists" do compile_stub(:evaluate_main) main_class = @known_resource_types.add Puppet::Resource::Type.new(:hostclass, "") main_class.expects(:evaluate_code).with { |r| r.is_a?(Puppet::Parser::Resource) } @compiler.topscope.expects(:source=).with(main_class) @compiler.compile end it "should create a new, empty 'main' if no main class exists" do compile_stub(:evaluate_main) @compiler.compile @known_resource_types.find_hostclass("").should be_instance_of(Puppet::Resource::Type) end it "should add an edge between the main stage and main class" do @compiler.compile (stage = @compiler.catalog.resource(:stage, "main")).should be_instance_of(Puppet::Parser::Resource) (klass = @compiler.catalog.resource(:class, "")).should be_instance_of(Puppet::Parser::Resource) - @compiler.catalog.edge?(stage, klass).should be_true + @compiler.catalog.edge?(stage, klass).should be_truthy end it "should evaluate all added collections" do colls = [] # And when the collections fail to evaluate. colls << mock("coll1-false") colls << mock("coll2-false") colls.each { |c| c.expects(:evaluate).returns(false) } @compiler.add_collection(colls[0]) @compiler.add_collection(colls[1]) compile_stub(:evaluate_generators) @compiler.compile end it "should ignore builtin resources" do resource = resource(:file, "testing") @compiler.add_resource(@scope, resource) resource.expects(:evaluate).never @compiler.compile end it "should evaluate unevaluated resources" do resource = CompilerTestResource.new(:file, "testing") @compiler.add_resource(@scope, resource) # We have to now mark the resource as evaluated resource.expects(:evaluate).with { |*whatever| resource.evaluated = true } @compiler.compile end it "should not evaluate already-evaluated resources" do resource = resource(:file, "testing") resource.stubs(:evaluated?).returns true @compiler.add_resource(@scope, resource) resource.expects(:evaluate).never @compiler.compile end it "should evaluate unevaluated resources created by evaluating other resources" do resource = CompilerTestResource.new(:file, "testing") @compiler.add_resource(@scope, resource) resource2 = CompilerTestResource.new(:file, "other") # We have to now mark the resource as evaluated resource.expects(:evaluate).with { |*whatever| resource.evaluated = true; @compiler.add_resource(@scope, resource2) } resource2.expects(:evaluate).with { |*whatever| resource2.evaluated = true } @compiler.compile end describe "when finishing" do before do @compiler.send(:evaluate_main) @catalog = @compiler.catalog end def add_resource(name, parent = nil) resource = Puppet::Parser::Resource.new "file", name, :scope => @scope @compiler.add_resource(@scope, resource) @catalog.add_edge(parent, resource) if parent resource end it "should call finish() on all resources" do # Add a resource that does respond to :finish resource = Puppet::Parser::Resource.new "file", "finish", :scope => @scope resource.expects(:finish) @compiler.add_resource(@scope, resource) # And one that does not dnf_resource = stub_everything "dnf", :ref => "File[dnf]", :type => "file" @compiler.add_resource(@scope, dnf_resource) @compiler.send(:finish) end it "should call finish() in add_resource order" do resources = sequence('resources') resource1 = add_resource("finish1") resource1.expects(:finish).in_sequence(resources) resource2 = add_resource("finish2") resource2.expects(:finish).in_sequence(resources) @compiler.send(:finish) end it "should add each container's metaparams to its contained resources" do main = @catalog.resource(:class, :main) main[:noop] = true resource1 = add_resource("meh", main) @compiler.send(:finish) - resource1[:noop].should be_true + resource1[:noop].should be_truthy end it "should add metaparams recursively" do main = @catalog.resource(:class, :main) main[:noop] = true resource1 = add_resource("meh", main) resource2 = add_resource("foo", resource1) @compiler.send(:finish) - resource2[:noop].should be_true + resource2[:noop].should be_truthy end it "should prefer metaparams from immediate parents" do main = @catalog.resource(:class, :main) main[:noop] = true resource1 = add_resource("meh", main) resource2 = add_resource("foo", resource1) resource1[:noop] = false @compiler.send(:finish) - resource2[:noop].should be_false + resource2[:noop].should be_falsey end it "should merge tags downward" do main = @catalog.resource(:class, :main) main.tag("one") resource1 = add_resource("meh", main) resource1.tag "two" resource2 = add_resource("foo", resource1) @compiler.send(:finish) resource2.tags.should be_include("one") resource2.tags.should be_include("two") end it "should work if only middle resources have metaparams set" do main = @catalog.resource(:class, :main) resource1 = add_resource("meh", main) resource1[:noop] = true resource2 = add_resource("foo", resource1) @compiler.send(:finish) - resource2[:noop].should be_true + resource2[:noop].should be_truthy end end it "should return added resources in add order" do resource1 = resource(:file, "yay") @compiler.add_resource(@scope, resource1) resource2 = resource(:file, "youpi") @compiler.add_resource(@scope, resource2) @compiler.resources.should == [resource1, resource2] end it "should add resources that do not conflict with existing resources" do resource = resource(:file, "yay") @compiler.add_resource(@scope, resource) @compiler.catalog.should be_vertex(resource) end it "should fail to add resources that conflict with existing resources" do path = make_absolute("/foo") file1 = resource(:file, path) file2 = resource(:file, path) @compiler.add_resource(@scope, file1) lambda { @compiler.add_resource(@scope, file2) }.should raise_error(Puppet::Resource::Catalog::DuplicateResourceError) end it "should add an edge from the scope resource to the added resource" do resource = resource(:file, "yay") @compiler.add_resource(@scope, resource) @compiler.catalog.should be_edge(@scope.resource, resource) end it "should not add non-class resources that don't specify a stage to the 'main' stage" do main = @compiler.catalog.resource(:stage, :main) resource = resource(:file, "foo") @compiler.add_resource(@scope, resource) @compiler.catalog.should_not be_edge(main, resource) end it "should not add any parent-edges to stages" do stage = resource(:stage, "other") @compiler.add_resource(@scope, stage) @scope.resource = resource(:class, "foo") - @compiler.catalog.edge?(@scope.resource, stage).should be_false + @compiler.catalog.edge?(@scope.resource, stage).should be_falsey end it "should not attempt to add stages to other stages" do other_stage = resource(:stage, "other") second_stage = resource(:stage, "second") @compiler.add_resource(@scope, other_stage) @compiler.add_resource(@scope, second_stage) second_stage[:stage] = "other" - @compiler.catalog.edge?(other_stage, second_stage).should be_false + @compiler.catalog.edge?(other_stage, second_stage).should be_falsey end it "should have a method for looking up resources" do resource = resource(:yay, "foo") @compiler.add_resource(@scope, resource) @compiler.findresource("Yay[foo]").should equal(resource) end it "should be able to look resources up by type and title" do resource = resource(:yay, "foo") @compiler.add_resource(@scope, resource) @compiler.findresource("Yay", "foo").should equal(resource) end it "should not evaluate virtual defined resources" do resource = resource(:file, "testing") resource.virtual = true @compiler.add_resource(@scope, resource) resource.expects(:evaluate).never @compiler.compile end end describe "when evaluating collections" do it "should evaluate each collection" do 2.times { |i| coll = mock 'coll%s' % i @compiler.add_collection(coll) # This is the hard part -- we have to emulate the fact that # collections delete themselves if they are done evaluating. coll.expects(:evaluate).with do @compiler.delete_collection(coll) end } @compiler.compile end it "should not fail when there are unevaluated resource collections that do not refer to specific resources" do coll = stub 'coll', :evaluate => false coll.expects(:unresolved_resources).returns(nil) @compiler.add_collection(coll) lambda { @compiler.compile }.should_not raise_error end it "should fail when there are unevaluated resource collections that refer to a specific resource" do coll = stub 'coll', :evaluate => false coll.expects(:unresolved_resources).returns(:something) @compiler.add_collection(coll) lambda { @compiler.compile }.should raise_error(Puppet::ParseError, 'Failed to realize virtual resources something') end it "should fail when there are unevaluated resource collections that refer to multiple specific resources" do coll = stub 'coll', :evaluate => false coll.expects(:unresolved_resources).returns([:one, :two]) @compiler.add_collection(coll) lambda { @compiler.compile }.should raise_error(Puppet::ParseError, 'Failed to realize virtual resources one, two') end end describe "when evaluating relationships" do it "should evaluate each relationship with its catalog" do dep = stub 'dep' dep.expects(:evaluate).with(@compiler.catalog) @compiler.add_relationship dep @compiler.evaluate_relationships end end describe "when told to evaluate missing classes" do it "should fail if there's no source listed for the scope" do scope = stub 'scope', :source => nil proc { @compiler.evaluate_classes(%w{one two}, scope) }.should raise_error(Puppet::DevError) end it "should raise an error if a class is not found" do @scope.expects(:find_hostclass).with("notfound").returns(nil) lambda{ @compiler.evaluate_classes(%w{notfound}, @scope) }.should raise_error(Puppet::Error, /Could not find class/) end it "should raise an error when it can't find class" do klasses = {'foo'=>nil} @node.classes = klasses @compiler.topscope.expects(:find_hostclass).with('foo').returns(nil) lambda{ @compiler.compile }.should raise_error(Puppet::Error, /Could not find class foo for testnode/) end end describe "when evaluating found classes" do before do Puppet.settings[:data_binding_terminus] = "none" @class = stub 'class', :name => "my::class" @scope.stubs(:find_hostclass).with("myclass").returns(@class) @resource = stub 'resource', :ref => "Class[myclass]", :type => "file" end around do |example| Puppet.override( :environments => Puppet::Environments::Static.new(environment), :description => "Static loader for specs" ) do example.run end end it "should evaluate each class" do @compiler.catalog.stubs(:tag) @class.expects(:ensure_in_catalog).with(@scope) @scope.stubs(:class_scope).with(@class) @compiler.evaluate_classes(%w{myclass}, @scope) end describe "and the classes are specified as a hash with parameters" do before do @node.classes = {} @ast_obj = Puppet::Parser::AST::Leaf.new(:value => 'foo') end # Define the given class with default parameters def define_class(name, parameters) @node.classes[name] = parameters klass = Puppet::Resource::Type.new(:hostclass, name, :arguments => {'p1' => @ast_obj, 'p2' => @ast_obj}) @compiler.topscope.known_resource_types.add klass end def compile @catalog = @compiler.compile end it "should record which classes are evaluated" do classes = {'foo'=>{}, 'bar::foo'=>{}, 'bar'=>{}} classes.each { |c, params| define_class(c, params) } compile() classes.each { |name, p| @catalog.classes.should include(name) } end it "should provide default values for parameters that have no values specified" do define_class('foo', {}) compile() @catalog.resource(:class, 'foo')['p1'].should == "foo" end it "should use any provided values" do define_class('foo', {'p1' => 'real_value'}) compile() @catalog.resource(:class, 'foo')['p1'].should == "real_value" end it "should support providing some but not all values" do define_class('foo', {'p1' => 'real_value'}) compile() @catalog.resource(:class, 'Foo')['p1'].should == "real_value" @catalog.resource(:class, 'Foo')['p2'].should == "foo" end it "should ensure each node class is in catalog and has appropriate tags" do klasses = ['bar::foo'] @node.classes = klasses ast_obj = Puppet::Parser::AST::Leaf.new(:value => 'foo') klasses.each do |name| klass = Puppet::Resource::Type.new(:hostclass, name, :arguments => {'p1' => ast_obj, 'p2' => ast_obj}) @compiler.topscope.known_resource_types.add klass end catalog = @compiler.compile r2 = catalog.resources.detect {|r| r.title == 'Bar::Foo' } r2.tags.should == Puppet::Util::TagSet.new(['bar::foo', 'class', 'bar', 'foo']) end end it "should fail if required parameters are missing" do klass = {'foo'=>{'a'=>'one'}} @node.classes = klass klass = Puppet::Resource::Type.new(:hostclass, 'foo', :arguments => {'a' => nil, 'b' => nil}) @compiler.topscope.known_resource_types.add klass lambda { @compiler.compile }.should raise_error(Puppet::ParseError, "Must pass b to Class[Foo]") end it "should fail if invalid parameters are passed" do klass = {'foo'=>{'3'=>'one'}} @node.classes = klass klass = Puppet::Resource::Type.new(:hostclass, 'foo', :arguments => {}) @compiler.topscope.known_resource_types.add klass lambda { @compiler.compile }.should raise_error(Puppet::ParseError, "Invalid parameter: '3' on Class[Foo]") end it "should ensure class is in catalog without params" do @node.classes = klasses = {'foo'=>nil} foo = Puppet::Resource::Type.new(:hostclass, 'foo') @compiler.topscope.known_resource_types.add foo catalog = @compiler.compile catalog.classes.should include 'foo' end it "should not evaluate the resources created for found classes unless asked" do @compiler.catalog.stubs(:tag) @resource.expects(:evaluate).never @class.expects(:ensure_in_catalog).returns(@resource) @scope.stubs(:class_scope).with(@class) @compiler.evaluate_classes(%w{myclass}, @scope) end it "should immediately evaluate the resources created for found classes when asked" do @compiler.catalog.stubs(:tag) @resource.expects(:evaluate) @class.expects(:ensure_in_catalog).returns(@resource) @scope.stubs(:class_scope).with(@class) @compiler.evaluate_classes(%w{myclass}, @scope, false) end it "should skip classes that have already been evaluated" do @compiler.catalog.stubs(:tag) @scope.stubs(:class_scope).with(@class).returns(@scope) @compiler.expects(:add_resource).never @resource.expects(:evaluate).never Puppet::Parser::Resource.expects(:new).never @compiler.evaluate_classes(%w{myclass}, @scope, false) end it "should skip classes previously evaluated with different capitalization" do @compiler.catalog.stubs(:tag) @scope.stubs(:find_hostclass).with("MyClass").returns(@class) @scope.stubs(:class_scope).with(@class).returns(@scope) @compiler.expects(:add_resource).never @resource.expects(:evaluate).never Puppet::Parser::Resource.expects(:new).never @compiler.evaluate_classes(%w{MyClass}, @scope, false) end end describe "when evaluating AST nodes with no AST nodes present" do it "should do nothing" do @compiler.expects(:ast_nodes?).returns(false) @compiler.known_resource_types.expects(:nodes).never Puppet::Parser::Resource.expects(:new).never @compiler.send(:evaluate_ast_node) end end describe "when evaluating AST nodes with AST nodes present" do before do @compiler.known_resource_types.stubs(:nodes?).returns true # Set some names for our test @node.stubs(:names).returns(%w{a b c}) @compiler.known_resource_types.stubs(:node).with("a").returns(nil) @compiler.known_resource_types.stubs(:node).with("b").returns(nil) @compiler.known_resource_types.stubs(:node).with("c").returns(nil) # It should check this last, of course. @compiler.known_resource_types.stubs(:node).with("default").returns(nil) end it "should fail if the named node cannot be found" do proc { @compiler.send(:evaluate_ast_node) }.should raise_error(Puppet::ParseError) end it "should evaluate the first node class matching the node name" do node_class = stub 'node', :name => "c", :evaluate_code => nil @compiler.known_resource_types.stubs(:node).with("c").returns(node_class) node_resource = stub 'node resource', :ref => "Node[c]", :evaluate => nil, :type => "node" node_class.expects(:ensure_in_catalog).returns(node_resource) @compiler.compile end it "should match the default node if no matching node can be found" do node_class = stub 'node', :name => "default", :evaluate_code => nil @compiler.known_resource_types.stubs(:node).with("default").returns(node_class) node_resource = stub 'node resource', :ref => "Node[default]", :evaluate => nil, :type => "node" node_class.expects(:ensure_in_catalog).returns(node_resource) @compiler.compile end it "should evaluate the node resource immediately rather than using lazy evaluation" do node_class = stub 'node', :name => "c" @compiler.known_resource_types.stubs(:node).with("c").returns(node_class) node_resource = stub 'node resource', :ref => "Node[c]", :type => "node" node_class.expects(:ensure_in_catalog).returns(node_resource) node_resource.expects(:evaluate) @compiler.send(:evaluate_ast_node) end end describe "when evaluating node classes" do include PuppetSpec::Compiler describe "when provided classes in array format" do let(:node) { Puppet::Node.new('someone', :classes => ['something']) } describe "when the class exists" do it "should succeed if the class is already included" do manifest = <<-MANIFEST class something {} include something MANIFEST catalog = compile_to_catalog(manifest, node) catalog.resource('Class', 'Something').should_not be_nil end it "should evaluate the class without parameters if it's not already included" do manifest = "class something {}" catalog = compile_to_catalog(manifest, node) catalog.resource('Class', 'Something').should_not be_nil end end it "should fail if the class doesn't exist" do expect { compile_to_catalog('', node) }.to raise_error(Puppet::Error, /Could not find class something/) end end describe "when provided classes in hash format" do describe "for classes without parameters" do let(:node) { Puppet::Node.new('someone', :classes => {'something' => {}}) } describe "when the class exists" do it "should succeed if the class is already included" do manifest = <<-MANIFEST class something {} include something MANIFEST catalog = compile_to_catalog(manifest, node) catalog.resource('Class', 'Something').should_not be_nil end it "should evaluate the class if it's not already included" do manifest = <<-MANIFEST class something {} MANIFEST catalog = compile_to_catalog(manifest, node) catalog.resource('Class', 'Something').should_not be_nil end end it "should fail if the class doesn't exist" do expect { compile_to_catalog('', node) }.to raise_error(Puppet::Error, /Could not find class something/) end end describe "for classes with parameters" do let(:node) { Puppet::Node.new('someone', :classes => {'something' => {'configuron' => 'defrabulated'}}) } describe "when the class exists" do it "should fail if the class is already included" do manifest = <<-MANIFEST class something($configuron=frabulated) {} include something MANIFEST expect { compile_to_catalog(manifest, node) }.to raise_error(Puppet::Error, /Class\[Something\] is already declared/) end it "should evaluate the class if it's not already included" do manifest = <<-MANIFEST class something($configuron=frabulated) {} MANIFEST catalog = compile_to_catalog(manifest, node) resource = catalog.resource('Class', 'Something') resource['configuron'].should == 'defrabulated' end end it "should fail if the class doesn't exist" do expect { compile_to_catalog('', node) }.to raise_error(Puppet::Error, /Could not find class something/) end it 'evaluates classes declared with parameters before unparameterized classes' do node = Puppet::Node.new('someone', :classes => { 'app::web' => {}, 'app' => { 'port' => 8080 } }) manifest = <<-MANIFEST class app($port = 80) { } class app::web($port = $app::port) inherits app { notify { expected: message => "$port" } } MANIFEST catalog = compile_to_catalog(manifest, node) expect(catalog).to have_resource("Class[App]").with_parameter(:port, 8080) expect(catalog).to have_resource("Class[App::Web]") expect(catalog).to have_resource("Notify[expected]").with_parameter(:message, "8080") end end end end describe "when managing resource overrides" do before do @override = stub 'override', :ref => "File[/foo]", :type => "my" @resource = resource(:file, "/foo") end it "should be able to store overrides" do lambda { @compiler.add_override(@override) }.should_not raise_error end it "should apply overrides to the appropriate resources" do @compiler.add_resource(@scope, @resource) @resource.expects(:merge).with(@override) @compiler.add_override(@override) @compiler.compile end it "should accept overrides before the related resource has been created" do @resource.expects(:merge).with(@override) # First store the override @compiler.add_override(@override) # Then the resource @compiler.add_resource(@scope, @resource) # And compile, so they get resolved @compiler.compile end it "should fail if the compile is finished and resource overrides have not been applied" do @compiler.add_override(@override) lambda { @compiler.compile }.should raise_error Puppet::ParseError, 'Could not find resource(s) File[/foo] for overriding' end end context "when converting catalog to resource" do it "the same environment is used for compilation as for transformation to resource form" do Puppet[:code] = <<-MANIFEST notify { 'dummy': } MANIFEST Puppet::Parser::Resource::Catalog.any_instance.expects(:to_resource).with do |catalog| Puppet.lookup(:current_environment).name == :production end Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode")) end end end diff --git a/spec/integration/ssl/autosign_spec.rb b/spec/integration/ssl/autosign_spec.rb index 2812d1fcc..68fde58f4 100644 --- a/spec/integration/ssl/autosign_spec.rb +++ b/spec/integration/ssl/autosign_spec.rb @@ -1,130 +1,130 @@ require 'spec_helper' describe "autosigning" do include PuppetSpec::Files let(:puppet_dir) { tmpdir("ca_autosigning") } let(:csr_attributes_content) do { 'custom_attributes' => { '1.3.6.1.4.1.34380.2.0' => 'hostname.domain.com', '1.3.6.1.4.1.34380.2.1' => 'my passphrase', '1.3.6.1.4.1.34380.2.2' => # system IPs in hex [ 0xC0A80001, # 192.168.0.1 0xC0A80101 ], # 192.168.1.1 }, 'extension_requests' => { 'pp_uuid' => 'abcdef', '1.3.6.1.4.1.34380.1.1.2' => '1234', # pp_instance_id '1.3.6.1.4.1.34380.1.2.1' => 'some-value', # private extension }, } end let(:host) { Puppet::SSL::Host.new } before do Puppet.settings[:confdir] = puppet_dir Puppet.settings[:vardir] = puppet_dir # This is necessary so the terminus instances don't lie around. Puppet::SSL::Key.indirection.termini.clear end def write_csr_attributes(yaml) File.open(Puppet.settings[:csr_attributes], 'w') do |file| file.puts YAML.dump(yaml) end end context "when the csr_attributes file is valid, but empty" do it "generates a CSR when the file is empty" do Puppet::FileSystem.touch(Puppet.settings[:csr_attributes]) host.generate_certificate_request end it "generates a CSR when the file contains whitespace" do File.open(Puppet.settings[:csr_attributes], 'w') do |file| file.puts "\n\n" end host.generate_certificate_request end end context "when the csr_attributes file doesn't contain a YAML encoded hash" do it "raises when the file contains a string" do write_csr_attributes('a string') expect { host.generate_certificate_request }.to raise_error(Puppet::Error, /invalid CSR attributes, expected instance of Hash, received instance of String/) end it "raises when the file contains an empty array" do write_csr_attributes([]) expect { host.generate_certificate_request }.to raise_error(Puppet::Error, /invalid CSR attributes, expected instance of Hash, received instance of Array/) end end context "with extension requests from csr_attributes file" do let(:ca) { Puppet::SSL::CertificateAuthority.new } it "generates a CSR when the csr_attributes file is an empty hash" do write_csr_attributes(csr_attributes_content) host.generate_certificate_request end context "and subjectAltName" do it "raises an error if you include subjectAltName in csr_attributes" do csr_attributes_content['extension_requests']['subjectAltName'] = 'foo' write_csr_attributes(csr_attributes_content) expect { host.generate_certificate_request }.to raise_error(Puppet::Error, /subjectAltName.*conflicts with internally used extension request/) end it "properly merges subjectAltName when in settings" do Puppet.settings[:dns_alt_names] = 'althostname.nowhere' write_csr_attributes(csr_attributes_content) host.generate_certificate_request csr = Puppet::SSL::CertificateRequest.indirection.find(host.name) expect(csr.subject_alt_names).to include('DNS:althostname.nowhere') end end context "without subjectAltName" do before do write_csr_attributes(csr_attributes_content) host.generate_certificate_request end it "pulls extension attributes from the csr_attributes file into the certificate" do csr = Puppet::SSL::CertificateRequest.indirection.find(host.name) expect(csr.request_extensions).to have(3).items expect(csr.request_extensions).to include('oid' => 'pp_uuid', 'value' => 'abcdef') expect(csr.request_extensions).to include('oid' => 'pp_instance_id', 'value' => '1234') expect(csr.request_extensions).to include('oid' => '1.3.6.1.4.1.34380.1.2.1', 'value' => 'some-value') end it "copies extension requests to certificate" do cert = ca.sign(host.name) expect(cert.custom_extensions).to include('oid' => 'pp_uuid', 'value' => 'abcdef') expect(cert.custom_extensions).to include('oid' => 'pp_instance_id', 'value' => '1234') expect(cert.custom_extensions).to include('oid' => '1.3.6.1.4.1.34380.1.2.1', 'value' => 'some-value') end it "does not copy custom attributes to certificate" do cert = ca.sign(host.name) cert.custom_extensions.each do |ext| - expect(Puppet::SSL::Oids.subtree_of?('1.3.6.1.4.1.34380.2', ext['oid'])).to be_false + expect(Puppet::SSL::Oids.subtree_of?('1.3.6.1.4.1.34380.2', ext['oid'])).to be_falsey end end end end end diff --git a/spec/integration/ssl/host_spec.rb b/spec/integration/ssl/host_spec.rb index 18f0d17fc..46f1f1388 100755 --- a/spec/integration/ssl/host_spec.rb +++ b/spec/integration/ssl/host_spec.rb @@ -1,82 +1,82 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/ssl/host' describe Puppet::SSL::Host do include PuppetSpec::Files before do # Get a safe temporary file dir = tmpdir("host_integration_testing") Puppet.settings[:confdir] = dir Puppet.settings[:vardir] = dir Puppet.settings[:group] = Process.gid Puppet::SSL::Host.ca_location = :local @host = Puppet::SSL::Host.new("luke.madstop.com") @ca = Puppet::SSL::CertificateAuthority.new end after { Puppet::SSL::Host.ca_location = :none } it "should be considered a CA host if its name is equal to 'ca'" do Puppet::SSL::Host.new(Puppet::SSL::CA_NAME).should be_ca end describe "when managing its key" do it "should be able to generate and save a key" do @host.generate_key end it "should save the key such that the Indirector can find it" do @host.generate_key Puppet::SSL::Key.indirection.find(@host.name).content.to_s.should == @host.key.to_s end it "should save the private key into the :privatekeydir" do @host.generate_key File.read(File.join(Puppet.settings[:privatekeydir], "luke.madstop.com.pem")).should == @host.key.to_s end end describe "when managing its certificate request" do it "should be able to generate and save a certificate request" do @host.generate_certificate_request end it "should save the certificate request such that the Indirector can find it" do @host.generate_certificate_request Puppet::SSL::CertificateRequest.indirection.find(@host.name).content.to_s.should == @host.certificate_request.to_s end it "should save the private certificate request into the :privatekeydir" do @host.generate_certificate_request File.read(File.join(Puppet.settings[:requestdir], "luke.madstop.com.pem")).should == @host.certificate_request.to_s end end describe "when the CA host" do it "should never store its key in the :privatekeydir" do Puppet.settings.use(:main, :ssl, :ca) @ca = Puppet::SSL::Host.new(Puppet::SSL::Host.ca_name) @ca.generate_key - Puppet::FileSystem.exist?(File.join(Puppet[:privatekeydir], "ca.pem")).should be_false + Puppet::FileSystem.exist?(File.join(Puppet[:privatekeydir], "ca.pem")).should be_falsey end end it "should pass the verification of its own SSL store", :unless => Puppet.features.microsoft_windows? do @host.generate @ca = Puppet::SSL::CertificateAuthority.new @ca.sign(@host.name) - @host.ssl_store.verify(@host.certificate.content).should be_true + @host.ssl_store.verify(@host.certificate.content).should be_truthy end end diff --git a/spec/integration/transaction_spec.rb b/spec/integration/transaction_spec.rb index 7ae28411a..91ca8129b 100755 --- a/spec/integration/transaction_spec.rb +++ b/spec/integration/transaction_spec.rb @@ -1,398 +1,398 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/transaction' describe Puppet::Transaction do include PuppetSpec::Files before do Puppet::Util::Storage.stubs(:store) end def mk_catalog(*resources) catalog = Puppet::Resource::Catalog.new(Puppet::Node.new("mynode")) resources.each { |res| catalog.add_resource res } catalog end def touch_path Puppet.features.microsoft_windows? ? "#{ENV['windir']}/system32" : "/usr/bin:/bin" end def usr_bin_touch(path) Puppet.features.microsoft_windows? ? "#{ENV['windir']}/system32/cmd.exe /c \"type NUL >> \"#{path}\"\"" : "/usr/bin/touch #{path}" end def touch(path) Puppet.features.microsoft_windows? ? "cmd.exe /c \"type NUL >> \"#{path}\"\"" : "touch #{path}" end it "should not apply generated resources if the parent resource fails" do catalog = Puppet::Resource::Catalog.new resource = Puppet::Type.type(:file).new :path => make_absolute("/foo/bar"), :backup => false catalog.add_resource resource child_resource = Puppet::Type.type(:file).new :path => make_absolute("/foo/bar/baz"), :backup => false resource.expects(:eval_generate).returns([child_resource]) transaction = Puppet::Transaction.new(catalog, nil, Puppet::Graph::RandomPrioritizer.new) resource.expects(:retrieve).raises "this is a failure" resource.stubs(:err) child_resource.expects(:retrieve).never transaction.evaluate end it "should not apply virtual resources" do catalog = Puppet::Resource::Catalog.new resource = Puppet::Type.type(:file).new :path => make_absolute("/foo/bar"), :backup => false resource.virtual = true catalog.add_resource resource transaction = Puppet::Transaction.new(catalog, nil, Puppet::Graph::RandomPrioritizer.new) resource.expects(:evaluate).never transaction.evaluate end it "should apply exported resources" do catalog = Puppet::Resource::Catalog.new path = tmpfile("exported_files") resource = Puppet::Type.type(:file).new :path => path, :backup => false, :ensure => :file resource.exported = true catalog.add_resource resource catalog.apply - Puppet::FileSystem.exist?(path).should be_true + Puppet::FileSystem.exist?(path).should be_truthy end it "should not apply virtual exported resources" do catalog = Puppet::Resource::Catalog.new resource = Puppet::Type.type(:file).new :path => make_absolute("/foo/bar"), :backup => false resource.exported = true resource.virtual = true catalog.add_resource resource transaction = Puppet::Transaction.new(catalog, nil, Puppet::Graph::RandomPrioritizer.new) resource.expects(:evaluate).never transaction.evaluate end it "should not apply device resources on normal host" do catalog = Puppet::Resource::Catalog.new resource = Puppet::Type.type(:interface).new :name => "FastEthernet 0/1" catalog.add_resource resource transaction = Puppet::Transaction.new(catalog, nil, Puppet::Graph::RandomPrioritizer.new) transaction.for_network_device = false transaction.expects(:apply).never.with(resource, nil) transaction.evaluate transaction.resource_status(resource).should be_skipped end it "should not apply host resources on device" do catalog = Puppet::Resource::Catalog.new resource = Puppet::Type.type(:file).new :path => make_absolute("/foo/bar"), :backup => false catalog.add_resource resource transaction = Puppet::Transaction.new(catalog, nil, Puppet::Graph::RandomPrioritizer.new) transaction.for_network_device = true transaction.expects(:apply).never.with(resource, nil) transaction.evaluate transaction.resource_status(resource).should be_skipped end it "should apply device resources on device" do catalog = Puppet::Resource::Catalog.new resource = Puppet::Type.type(:interface).new :name => "FastEthernet 0/1" catalog.add_resource resource transaction = Puppet::Transaction.new(catalog, nil, Puppet::Graph::RandomPrioritizer.new) transaction.for_network_device = true transaction.expects(:apply).with(resource, nil) transaction.evaluate transaction.resource_status(resource).should_not be_skipped end it "should apply resources appliable on host and device on a device" do catalog = Puppet::Resource::Catalog.new resource = Puppet::Type.type(:schedule).new :name => "test" catalog.add_resource resource transaction = Puppet::Transaction.new(catalog, nil, Puppet::Graph::RandomPrioritizer.new) transaction.for_network_device = true transaction.expects(:apply).with(resource, nil) transaction.evaluate transaction.resource_status(resource).should_not be_skipped end # Verify that one component requiring another causes the contained # resources in the requiring component to get refreshed. it "should propagate events from a contained resource through its container to its dependent container's contained resources" do transaction = nil file = Puppet::Type.type(:file).new :path => tmpfile("event_propagation"), :ensure => :present execfile = File.join(tmpdir("exec_event"), "exectestingness2") exec = Puppet::Type.type(:exec).new :command => touch(execfile), :path => ENV['PATH'] catalog = mk_catalog(file) fcomp = Puppet::Type.type(:component).new(:name => "Foo[file]") catalog.add_resource fcomp catalog.add_edge(fcomp, file) ecomp = Puppet::Type.type(:component).new(:name => "Foo[exec]") catalog.add_resource ecomp catalog.add_resource exec catalog.add_edge(ecomp, exec) ecomp[:subscribe] = Puppet::Resource.new(:foo, "file") exec[:refreshonly] = true exec.expects(:refresh) catalog.apply end # Make sure that multiple subscriptions get triggered. it "should propagate events to all dependent resources" do path = tmpfile("path") file1 = tmpfile("file1") file2 = tmpfile("file2") file = Puppet::Type.type(:file).new( :path => path, :ensure => "file" ) exec1 = Puppet::Type.type(:exec).new( :path => ENV["PATH"], :command => touch(file1), :refreshonly => true, :subscribe => Puppet::Resource.new(:file, path) ) exec2 = Puppet::Type.type(:exec).new( :path => ENV["PATH"], :command => touch(file2), :refreshonly => true, :subscribe => Puppet::Resource.new(:file, path) ) catalog = mk_catalog(file, exec1, exec2) catalog.apply - Puppet::FileSystem.exist?(file1).should be_true - Puppet::FileSystem.exist?(file2).should be_true + Puppet::FileSystem.exist?(file1).should be_truthy + Puppet::FileSystem.exist?(file2).should be_truthy end it "does not refresh resources that have 'noop => true'" do path = tmpfile("path") notify = Puppet::Type.type(:notify).new( :name => "trigger", :notify => Puppet::Resource.new(:exec, "noop exec") ) noop_exec = Puppet::Type.type(:exec).new( :name => "noop exec", :path => ENV["PATH"], :command => touch(path), :noop => true ) catalog = mk_catalog(notify, noop_exec) catalog.apply - Puppet::FileSystem.exist?(path).should be_false + Puppet::FileSystem.exist?(path).should be_falsey end it "should apply no resources whatsoever if a pre_run_check fails" do path = tmpfile("path") file = Puppet::Type.type(:file).new( :path => path, :ensure => "file" ) notify = Puppet::Type.type(:notify).new( :title => "foo" ) notify.expects(:pre_run_check).raises(Puppet::Error, "fail for testing") catalog = mk_catalog(file, notify) catalog.apply - Puppet::FileSystem.exist?(path).should_not be_true + Puppet::FileSystem.exist?(path).should_not be_truthy end it "should not let one failed refresh result in other refreshes failing" do path = tmpfile("path") newfile = tmpfile("file") file = Puppet::Type.type(:file).new( :path => path, :ensure => "file" ) exec1 = Puppet::Type.type(:exec).new( :path => ENV["PATH"], :command => touch(File.expand_path("/this/cannot/possibly/exist")), :logoutput => true, :refreshonly => true, :subscribe => file, :title => "one" ) exec2 = Puppet::Type.type(:exec).new( :path => ENV["PATH"], :command => touch(newfile), :logoutput => true, :refreshonly => true, :subscribe => [file, exec1], :title => "two" ) exec1.stubs(:err) catalog = mk_catalog(file, exec1, exec2) catalog.apply - Puppet::FileSystem.exist?(newfile).should be_true + Puppet::FileSystem.exist?(newfile).should be_truthy end describe "skipping resources" do let(:fname) { tmpfile("exec") } let(:file) do Puppet::Type.type(:file).new( :name => tmpfile("file"), :ensure => "file", :backup => false ) end let(:exec) do Puppet::Type.type(:exec).new( :name => touch(fname), :path => touch_path, :subscribe => Puppet::Resource.new("file", file.name) ) end it "does not trigger unscheduled resources" do catalog = mk_catalog catalog.add_resource(*Puppet::Type.type(:schedule).mkdefaultschedules) Puppet[:ignoreschedules] = false exec[:schedule] = "monthly" catalog.add_resource(file, exec) # Run it once so further runs don't schedule the resource catalog.apply - expect(Puppet::FileSystem.exist?(fname)).to be_true + expect(Puppet::FileSystem.exist?(fname)).to be_truthy # Now remove it, so it can get created again Puppet::FileSystem.unlink(fname) file[:content] = "some content" catalog.apply - expect(Puppet::FileSystem.exist?(fname)).to be_false + expect(Puppet::FileSystem.exist?(fname)).to be_falsey end it "does not trigger untagged resources" do catalog = mk_catalog Puppet[:tags] = "runonly" file.tag("runonly") catalog.add_resource(file, exec) catalog.apply - expect(Puppet::FileSystem.exist?(fname)).to be_false + expect(Puppet::FileSystem.exist?(fname)).to be_falsey end it "does not trigger resources with failed dependencies" do catalog = mk_catalog file[:path] = make_absolute("/foo/bar/baz") catalog.add_resource(file, exec) catalog.apply - expect(Puppet::FileSystem.exist?(fname)).to be_false + expect(Puppet::FileSystem.exist?(fname)).to be_falsey end end it "should not attempt to evaluate resources with failed dependencies" do exec = Puppet::Type.type(:exec).new( :command => "#{File.expand_path('/bin/mkdir')} /this/path/cannot/possibly/exist", :title => "mkdir" ) file1 = Puppet::Type.type(:file).new( :title => "file1", :path => tmpfile("file1"), :require => exec, :ensure => :file ) file2 = Puppet::Type.type(:file).new( :title => "file2", :path => tmpfile("file2"), :require => file1, :ensure => :file ) catalog = mk_catalog(exec, file1, file2) catalog.apply - Puppet::FileSystem.exist?(file1[:path]).should be_false - Puppet::FileSystem.exist?(file2[:path]).should be_false + Puppet::FileSystem.exist?(file1[:path]).should be_falsey + Puppet::FileSystem.exist?(file2[:path]).should be_falsey end it "should not trigger subscribing resources on failure" do file1 = tmpfile("file1") file2 = tmpfile("file2") create_file1 = Puppet::Type.type(:exec).new( :command => usr_bin_touch(file1) ) exec = Puppet::Type.type(:exec).new( :command => "#{File.expand_path('/bin/mkdir')} /this/path/cannot/possibly/exist", :title => "mkdir", :notify => create_file1 ) create_file2 = Puppet::Type.type(:exec).new( :command => usr_bin_touch(file2), :subscribe => exec ) catalog = mk_catalog(exec, create_file1, create_file2) catalog.apply - Puppet::FileSystem.exist?(file1).should be_false - Puppet::FileSystem.exist?(file2).should be_false + Puppet::FileSystem.exist?(file1).should be_falsey + Puppet::FileSystem.exist?(file2).should be_falsey end # #801 -- resources only checked in noop should be rescheduled immediately. it "should immediately reschedule noop resources" do Puppet::Type.type(:schedule).mkdefaultschedules resource = Puppet::Type.type(:notify).new(:name => "mymessage", :noop => true) catalog = Puppet::Resource::Catalog.new catalog.add_resource resource trans = catalog.apply trans.resource_harness.should be_scheduled(resource) end end diff --git a/spec/integration/type/exec_spec.rb b/spec/integration/type/exec_spec.rb index 1e39bdb9f..e127ac564 100755 --- a/spec/integration/type/exec_spec.rb +++ b/spec/integration/type/exec_spec.rb @@ -1,77 +1,77 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/files' describe Puppet::Type.type(:exec) do include PuppetSpec::Files let(:catalog) { Puppet::Resource::Catalog.new } let(:path) { tmpfile('exec_provider') } let(:command) { "ruby -e 'File.open(\"#{path}\", \"w\") { |f| f.print \"foo\" }'" } before :each do catalog.host_config = false end it "should execute the command" do exec = described_class.new :command => command, :path => ENV['PATH'] catalog.add_resource exec catalog.apply File.read(path).should == 'foo' end it "should not execute the command if onlyif returns non-zero" do exec = described_class.new( :command => command, :onlyif => "ruby -e 'exit 44'", :path => ENV['PATH'] ) catalog.add_resource exec catalog.apply - Puppet::FileSystem.exist?(path).should be_false + Puppet::FileSystem.exist?(path).should be_falsey end it "should execute the command if onlyif returns zero" do exec = described_class.new( :command => command, :onlyif => "ruby -e 'exit 0'", :path => ENV['PATH'] ) catalog.add_resource exec catalog.apply File.read(path).should == 'foo' end it "should execute the command if unless returns non-zero" do exec = described_class.new( :command => command, :unless => "ruby -e 'exit 45'", :path => ENV['PATH'] ) catalog.add_resource exec catalog.apply File.read(path).should == 'foo' end it "should not execute the command if unless returns zero" do exec = described_class.new( :command => command, :unless => "ruby -e 'exit 0'", :path => ENV['PATH'] ) catalog.add_resource exec catalog.apply - Puppet::FileSystem.exist?(path).should be_false + Puppet::FileSystem.exist?(path).should be_falsey end end diff --git a/spec/integration/type/file_spec.rb b/spec/integration/type/file_spec.rb index b2e9c2e99..25b0fb6fe 100755 --- a/spec/integration/type/file_spec.rb +++ b/spec/integration/type/file_spec.rb @@ -1,1300 +1,1300 @@ #! /usr/bin/env ruby 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), :uses_checksums => true do include PuppetSpec::Files let(:catalog) { Puppet::Resource::Catalog.new } 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 let(:dir) 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, 'dir_testing') end if Puppet.features.posix? def set_mode(mode, file) File.chmod(mode, file) end def get_mode(file) Puppet::FileSystem.lstat(file).mode end def get_owner(file) Puppet::FileSystem.lstat(file).uid end def get_group(file) Puppet::FileSystem.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 def get_aces_for_path_by_sid(path, sid) SecurityHelper.get_aces_for_path_by_sid(path, sid) end end around :each do |example| Puppet.override(:environments => Puppet::Environments::Static.new) do example.run 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 source = tmpfile('source') catalog.add_resource described_class.new :path => source, :mode => '0755' status = catalog.apply.report.resource_statuses["File[#{source}]"] status.should_not be_failed status.should_not be_changed - Puppet::FileSystem.exist?(source).should be_false + Puppet::FileSystem.exist?(source).should be_falsey 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 - Puppet::FileSystem.exist?(path).should be_false + Puppet::FileSystem.exist?(path).should be_falsey 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 - Puppet::FileSystem.exist?(path).should be_false + Puppet::FileSystem.exist?(path).should be_falsey 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 target = tmpfile_with_contents('target', '') owner = get_owner(target) catalog.add_resource described_class.new( :name => target, :owner => owner ) catalog.apply get_owner(target).should == owner end it "should set the group" do target = tmpfile_with_contents('target', '') group = get_group(target) catalog.add_resource described_class.new( :name => target, :group => group ) catalog.apply get_group(target).should == group end describe "when setting mode" do describe "for directories" do let(:target) { tmpdir('dir_mode') } it "should set executable bits for newly created directories" do catalog.add_resource described_class.new(:path => target, :ensure => :directory, :mode => '0600') catalog.apply (get_mode(target) & 07777).should == 0700 end it "should set executable bits for existing readable directories" do set_mode(0600, target) catalog.add_resource described_class.new(:path => target, :ensure => :directory, :mode => '0644') catalog.apply (get_mode(target) & 07777).should == 0755 end it "should not set executable bits for unreadable directories" do begin catalog.add_resource described_class.new(:path => target, :ensure => :directory, :mode => '0300') catalog.apply (get_mode(target) & 07777).should == 0300 ensure # so we can cleanup set_mode(0700, target) end end it "should set user, group, and other executable bits" do catalog.add_resource described_class.new(:path => target, :ensure => :directory, :mode => '0664') catalog.apply (get_mode(target) & 07777).should == 0775 end it "should set executable bits when overwriting a non-executable file" do target_path = tmpfile_with_contents('executable', '') set_mode(0444, target_path) catalog.add_resource described_class.new(:path => target_path, :ensure => :directory, :mode => '0666', :backup => false) catalog.apply (get_mode(target_path) & 07777).should == 0777 File.should be_directory(target_path) end end describe "for files" do 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", :if => described_class.defaultprovider.feature?(:manages_symlinks) do let(:link) { tmpfile('link_mode') } describe "when managing links" do let(:link_target) { tmpfile('target') } before :each do FileUtils.touch(link_target) File.chmod(0444, link_target) Puppet::FileSystem.symlink(link_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 => link_target, :links => :manage) catalog.apply (Puppet::FileSystem.stat(link).mode & 07777) == 0666 (Puppet::FileSystem.lstat(link_target).mode & 07777) == 0444 end it "should ignore dangling symlinks (#6856)" do File.delete(link_target) catalog.add_resource described_class.new(:path => link, :ensure => :link, :mode => '0666', :target => link_target, :links => :manage) catalog.apply - Puppet::FileSystem.exist?(link).should be_false + Puppet::FileSystem.exist?(link).should be_falsey end it "should create a link to the target if ensure is omitted" do FileUtils.touch(link_target) catalog.add_resource described_class.new(:path => link, :target => link_target) catalog.apply - Puppet::FileSystem.exist?(link).should be_true + Puppet::FileSystem.exist?(link).should be_truthy Puppet::FileSystem.lstat(link).ftype.should == 'link' Puppet::FileSystem.readlink(link).should == link_target end end describe "when following links" do it "should ignore dangling symlinks (#6856)" do target = tmpfile('dangling') FileUtils.touch(target) Puppet::FileSystem.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(:link_target) { tmpdir('dir_target') } before :each do File.chmod(0600, link_target) Puppet::FileSystem.symlink(link_target, link) end after :each do File.chmod(0750, link_target) end describe "that is readable" do it "should set the executable bits when creating the destination (#10315)" do catalog.add_resource described_class.new(:path => path, :source => link, :mode => '0666', :links => :follow) catalog.apply File.should be_directory(path) (get_mode(path) & 07777).should == 0777 end it "should set the executable bits when overwriting the destination (#10315)" do FileUtils.touch(path) catalog.add_resource described_class.new(:path => path, :source => link, :mode => '0666', :links => :follow, :backup => false) catalog.apply File.should be_directory(path) (get_mode(path) & 07777).should == 0777 end end describe "that is not readable" do before :each do set_mode(0300, link_target) end # so we can cleanup after :each do set_mode(0700, link_target) end it "should set executable bits when creating the destination (#10315)" do catalog.add_resource described_class.new(:path => path, :source => link, :mode => '0666', :links => :follow) catalog.apply File.should be_directory(path) (get_mode(path) & 07777).should == 0777 end it "should 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, :backup => false) catalog.apply File.should be_directory(path) (get_mode(path) & 07777).should == 0777 end end end describe "to a file" do let(:link_target) { tmpfile('file_target') } before :each do FileUtils.touch(link_target) Puppet::FileSystem.symlink(link_target, link) end it "should create the file, not a symlink (#2817, #10315)" do 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).should == 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).should == 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 Puppet::FileSystem.symlink(real_target, target) Puppet::FileSystem.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 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).should == 0700 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) & 0111).should == 0100 end end end end end end end describe "when writing files" do with_digest_algorithms 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], "w") { |f| f.write("bar") } d = digest(IO.binread(file[:path])) catalog.apply filebucket.bucket.getfile(d).should == "bar" 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" - Puppet::FileSystem.exist?(backup).should be_true + Puppet::FileSystem.exist?(backup).should be_truthy File.read(backup).should == "bar\n" end it "should fail if no backup can be performed" do 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", :if => described_class.defaultprovider.feature?(:manages_symlinks) 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" } Puppet::FileSystem.symlink(dest1, link) d = digest(File.read(file[:path])) catalog.apply Puppet::FileSystem.readlink(link).should == dest2 - Puppet::FileSystem.exist?(bucket[:path]).should be_false + Puppet::FileSystem.exist?(bucket[:path]).should be_falsey 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" } food = digest(File.read(foofile)) bard = digest(File.read(barfile)) catalog.apply bucket.bucket.getfile(food).should == "fooyay" bucket.bucket.getfile(bard).should == "baryay" end 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", :if => described_class.defaultprovider.feature?(:manages_symlinks) 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) Puppet::FileSystem.lstat(link_path).should be_directory end @files.each do |path| link_path = path.sub(source, dest) Puppet::FileSystem.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) Puppet::FileSystem.lstat(newpath).should be_directory end @files.each do |path| newpath = path.sub(source, dest) Puppet::FileSystem.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") FileUtils.mkdir_p(path) FileUtils.touch(managed) FileUtils.touch(generated) catalog.add_resource described_class.new(:name => path, :recurse => true, :backup => false, :mode => '0700') catalog.add_resource described_class.new(:name => managed, :recurse => true, :backup => false, :mode => "644") catalog.apply (get_mode(generated) & 007777).should == 0700 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')) catalog.add_resource Puppet::Type.newfile( :path => path, :ensure => :directory, :backup => false, :recurse => true, :sourceselect => :first, :source => [one, two] ) catalog.apply File.should be_directory(path) - Puppet::FileSystem.exist?(File.join(path, 'one')).should be_false - Puppet::FileSystem.exist?(File.join(path, 'three', 'four')).should be_true + Puppet::FileSystem.exist?(File.join(path, 'one')).should be_falsey + Puppet::FileSystem.exist?(File.join(path, 'three', 'four')).should be_truthy end it "should recursively copy an empty directory" do one = File.expand_path('thisdoesnotexist') two = tmpdir('two') three = tmpdir('three') file_in_dir_with_contents(three, 'a', '') catalog.add_resource Puppet::Type.newfile( :path => path, :ensure => :directory, :backup => false, :recurse => true, :sourceselect => :first, :source => [one, two, three] ) catalog.apply File.should be_directory(path) - Puppet::FileSystem.exist?(File.join(path, 'a')).should be_false + Puppet::FileSystem.exist?(File.join(path, 'a')).should be_falsey 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')) catalog.add_resource Puppet::Type.newfile( :path => path, :ensure => :directory, :backup => false, :recurse => true, :recurselimit => 1, :sourceselect => :first, :source => [one, two] ) catalog.apply - Puppet::FileSystem.exist?(File.join(path, 'a')).should be_true - Puppet::FileSystem.exist?(File.join(path, 'a', 'b')).should be_false - Puppet::FileSystem.exist?(File.join(path, 'z')).should be_false + Puppet::FileSystem.exist?(File.join(path, 'a')).should be_truthy + Puppet::FileSystem.exist?(File.join(path, 'a', 'b')).should be_falsey + Puppet::FileSystem.exist?(File.join(path, 'z')).should be_falsey end end describe "for a file" do it "should copy the first file that exists" do one = File.expand_path('thisdoesnotexist') two = tmpfile_with_contents('two', 'yay') three = tmpfile_with_contents('three', 'no') catalog.add_resource Puppet::Type.newfile( :path => path, :ensure => :file, :backup => false, :sourceselect => :first, :source => [one, two, three] ) catalog.apply File.read(path).should == 'yay' end it "should copy an empty file" do one = File.expand_path('thisdoesnotexist') two = tmpfile_with_contents('two', '') three = tmpfile_with_contents('three', 'no') catalog.add_resource Puppet::Type.newfile( :path => path, :ensure => :file, :backup => false, :sourceselect => :first, :source => [one, two, three] ) 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 dest = tmpdir('dest') one = tmpdir('one') two = tmpdir('two') three = tmpdir('three') four = tmpdir('four') file_in_dir_with_contents(one, 'a', one) file_in_dir_with_contents(two, 'a', two) file_in_dir_with_contents(two, 'b', two) file_in_dir_with_contents(three, 'a', three) file_in_dir_with_contents(three, 'c', three) obj = Puppet::Type.newfile( :path => dest, :ensure => :directory, :backup => false, :recurse => true, :sourceselect => :all, :source => [one, two, three, four] ) catalog.add_resource obj catalog.apply File.read(File.join(dest, 'a')).should == one File.read(File.join(dest, 'b')).should == two File.read(File.join(dest, '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 - Puppet::FileSystem.exist?(File.join(path, 'a')).should be_true - Puppet::FileSystem.exist?(File.join(path, 'a', 'b')).should be_false - Puppet::FileSystem.exist?(File.join(path, 'z')).should be_true - Puppet::FileSystem.exist?(File.join(path, 'z', 'y')).should be_false + Puppet::FileSystem.exist?(File.join(path, 'a')).should be_truthy + Puppet::FileSystem.exist?(File.join(path, 'a', 'b')).should be_falsey + Puppet::FileSystem.exist?(File.join(path, 'z')).should be_truthy + Puppet::FileSystem.exist?(File.join(path, 'z', 'y')).should be_falsey end end end end end describe "when generating resources" do before do source = tmpdir("generating_in_catalog_source") s1 = file_in_dir_with_contents(source, "one", "uno") s2 = file_in_dir_with_contents(source, "two", "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 it "should be able to copy files with pound signs in their names (#285)" do source = tmpfile_with_contents("filewith#signs", "foo") dest = tmpfile("destwith#signs") catalog.add_resource described_class.new(:name => dest, :source => source) catalog.apply File.read(dest).should == "foo" end it "should be able to copy files with spaces in their names" do dest = tmpfile("destwith spaces") source = tmpfile_with_contents("filewith spaces", "foo") catalog.add_resource described_class.new(:path => dest, :source => source) catalog.apply File.read(dest).should == "foo" end it "should be able to copy individual files even if recurse has been specified" do source = tmpfile_with_contents("source", "foo") dest = tmpfile("dest") catalog.add_resource described_class.new(:name => dest, :source => source, :recurse => true) catalog.apply File.read(dest).should == "foo" end end it "should create a file with content if ensure is omitted" do catalog.add_resource described_class.new( :path => path, :content => "this is some content, yo" ) 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_with_contents("source_source_with_ensure", "yay") dest = tmpfile_with_contents("source_source_with_ensure", "boo") file = described_class.new( :path => dest, :ensure => :absent, :source => source, :backup => false ) catalog.add_resource file catalog.apply - Puppet::FileSystem.exist?(dest).should be_false + Puppet::FileSystem.exist?(dest).should be_falsey end describe "when sourcing" do let(:source) { tmpfile_with_contents("source_default_values", "yay") } describe "on POSIX systems", :if => Puppet.features.posix? do it "should apply the source metadata values" do set_mode(0770, source) file = described_class.new( :path => path, :ensure => :file, :source => source, :source_permissions => :use, :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 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 def expects_sid_granted_full_access_explicitly(path, sid) inherited_ace = Puppet::Util::Windows::AccessControlEntry::INHERITED_ACE aces = get_aces_for_path_by_sid(path, sid) aces.should_not be_empty aces.each do |ace| ace.mask.should == Puppet::Util::Windows::File::FILE_ALL_ACCESS (ace.flags & inherited_ace).should_not == inherited_ace end end def expects_system_granted_full_access_explicitly(path) expects_sid_granted_full_access_explicitly(path, @sids[:system]) end def expects_at_least_one_inherited_ace_grants_full_access(path, sid) inherited_ace = Puppet::Util::Windows::AccessControlEntry::INHERITED_ACE aces = get_aces_for_path_by_sid(path, sid) aces.should_not be_empty aces.any? do |ace| ace.mask == Puppet::Util::Windows::File::FILE_ALL_ACCESS && (ace.flags & inherited_ace) == inherited_ace - end.should be_true + end.should be_truthy end def expects_at_least_one_inherited_system_ace_grants_full_access(path) expects_at_least_one_inherited_ace_grants_full_access(path, @sids[:system]) end describe "when processing SYSTEM ACEs" do before do @sids = { :current_user => Puppet::Util::Windows::SID.name_to_sid(Puppet::Util::Windows::ADSI::User.current_user_name), :system => Win32::Security::SID::LocalSystem, :admin => Puppet::Util::Windows::SID.name_to_sid("Administrator"), :guest => Puppet::Util::Windows::SID.name_to_sid("Guest"), :users => Win32::Security::SID::BuiltinUsers, :power_users => Win32::Security::SID::PowerUsers, :none => Win32::Security::SID::Nobody } end describe "on files" do before :each do @file = described_class.new( :path => path, :ensure => :file, :source => source, :backup => false ) catalog.add_resource @file end describe "when permissions are not insync?" do before :each do @file[:owner] = 'None' @file[:group] = 'None' end it "preserves the inherited SYSTEM ACE for an existing file" do FileUtils.touch(path) expects_at_least_one_inherited_system_ace_grants_full_access(path) catalog.apply expects_at_least_one_inherited_system_ace_grants_full_access(path) end it "applies the inherited SYSTEM ACEs for a new file" do catalog.apply expects_at_least_one_inherited_system_ace_grants_full_access(path) end end describe "created with SYSTEM as the group" do before :each do @file[:owner] = @sids[:users] @file[:group] = @sids[:system] @file[:mode] = '0644' catalog.apply end it "should allow the user to explicitly set the mode to 4" do system_aces = get_aces_for_path_by_sid(path, @sids[:system]) system_aces.should_not be_empty system_aces.each do |ace| ace.mask.should == Puppet::Util::Windows::File::FILE_GENERIC_READ end end it "prepends SYSTEM ace when changing group from system to power users" do @file[:group] = @sids[:power_users] catalog.apply system_aces = get_aces_for_path_by_sid(path, @sids[:system]) system_aces.size.should == 1 end end describe "with :links set to :follow" do it "should not fail to apply" do # at minimal, we need an owner and/or group @file[:owner] = @sids[:users] @file[:links] = :follow catalog.apply do |transaction| if transaction.any_failed? pretty_transaction_error(transaction) end end end end end describe "on directories" do before :each do @directory = described_class.new( :path => dir, :ensure => :directory ) catalog.add_resource @directory end def grant_everyone_full_access(path) sd = Puppet::Util::Windows::Security.get_security_descriptor(path) sd.dacl.allow( 'S-1-1-0', #everyone Puppet::Util::Windows::File::FILE_ALL_ACCESS, Puppet::Util::Windows::AccessControlEntry::OBJECT_INHERIT_ACE | Puppet::Util::Windows::AccessControlEntry::CONTAINER_INHERIT_ACE) Puppet::Util::Windows::Security.set_security_descriptor(path, sd) end after :each do grant_everyone_full_access(dir) end describe "when permissions are not insync?" do before :each do @directory[:owner] = 'None' @directory[:group] = 'None' end it "preserves the inherited SYSTEM ACEs for an existing directory" do FileUtils.mkdir(dir) expects_at_least_one_inherited_system_ace_grants_full_access(dir) catalog.apply expects_at_least_one_inherited_system_ace_grants_full_access(dir) end it "applies the inherited SYSTEM ACEs for a new directory" do catalog.apply expects_at_least_one_inherited_system_ace_grants_full_access(dir) end describe "created with SYSTEM as the group" do before :each do @directory[:owner] = @sids[:users] @directory[:group] = @sids[:system] @directory[:mode] = '0644' catalog.apply end it "should allow the user to explicitly set the mode to 4" do system_aces = get_aces_for_path_by_sid(dir, @sids[:system]) system_aces.should_not be_empty system_aces.each do |ace| # unlike files, Puppet sets execute bit on directories that are readable ace.mask.should == Puppet::Util::Windows::File::FILE_GENERIC_READ | Puppet::Util::Windows::File::FILE_GENERIC_EXECUTE end end it "prepends SYSTEM ace when changing group from system to power users" do @directory[:group] = @sids[:power_users] catalog.apply system_aces = get_aces_for_path_by_sid(dir, @sids[:system]) system_aces.size.should == 1 end end describe "with :links set to :follow" do it "should not fail to apply" do # at minimal, we need an owner and/or group @directory[:owner] = @sids[:users] @directory[:links] = :follow catalog.apply do |transaction| if transaction.any_failed? pretty_transaction_error(transaction) end end end end end end end end end describe "when purging files" do before do sourcedir = tmpdir("purge_source") destdir = tmpdir("purge_dest") 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 - Puppet::FileSystem.exist?(@purgee).should be_false + Puppet::FileSystem.exist?(@purgee).should be_falsey end end describe "when using validate_cmd" do it "should fail the file resource if command fails" do catalog.add_resource(described_class.new(:path => path, :content => "foo", :validate_cmd => "/usr/bin/env false")) Puppet::Util::Execution.expects(:execute).with("/usr/bin/env false", {:combine => true, :failonfail => true}).raises(Puppet::ExecutionFailure, "Failed") report = catalog.apply.report report.resource_statuses["File[#{path}]"].should be_failed - Puppet::FileSystem.exist?(path).should be_false + Puppet::FileSystem.exist?(path).should be_falsey end it "should succeed the file resource if command succeeds" do catalog.add_resource(described_class.new(:path => path, :content => "foo", :validate_cmd => "/usr/bin/env true")) Puppet::Util::Execution.expects(:execute).with("/usr/bin/env true", {:combine => true, :failonfail => true}).returns '' report = catalog.apply.report report.resource_statuses["File[#{path}]"].should_not be_failed - Puppet::FileSystem.exist?(path).should be_true + Puppet::FileSystem.exist?(path).should be_truthy end end def tmpfile_with_contents(name, contents) file = tmpfile(name) File.open(file, "w") { |f| f.write contents } file end def file_in_dir_with_contents(dir, name, contents) full_name = File.join(dir, name) File.open(full_name, "w") { |f| f.write contents } full_name end def pretty_transaction_error(transaction) report = transaction.report status_failures = report.resource_statuses.values.select { |r| r.failed? } status_fail_msg = status_failures. collect(&:events). flatten. select { |event| event.status == 'failure' }. collect { |event| "#{event.resource}: #{event.message}" }.join("; ") raise "Got #{status_failures.length} failure(s) while applying: #{status_fail_msg}" end end diff --git a/spec/integration/type/tidy_spec.rb b/spec/integration/type/tidy_spec.rb index 7dbefb6ca..7ba542801 100755 --- a/spec/integration/type/tidy_spec.rb +++ b/spec/integration/type/tidy_spec.rb @@ -1,34 +1,34 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/files' require 'puppet/file_bucket/dipper' describe Puppet::Type.type(:tidy) do include PuppetSpec::Files before do Puppet::Util::Storage.stubs(:store) end # Testing #355. it "should be able to remove dead links", :if => Puppet.features.manages_symlinks? do dir = tmpfile("tidy_link_testing") link = File.join(dir, "link") target = tmpfile("no_such_file_tidy_link_testing") Dir.mkdir(dir) Puppet::FileSystem.symlink(target, link) tidy = Puppet::Type.type(:tidy).new :path => dir, :recurse => true catalog = Puppet::Resource::Catalog.new catalog.add_resource(tidy) # avoid crude failures because of nil resources that result # from implicit containment and lacking containers catalog.stubs(:container_of).returns tidy catalog.apply - Puppet::FileSystem.symlink?(link).should be_false + Puppet::FileSystem.symlink?(link).should be_falsey end end diff --git a/spec/integration/util/autoload_spec.rb b/spec/integration/util/autoload_spec.rb index 997e43074..0d98bf477 100755 --- a/spec/integration/util/autoload_spec.rb +++ b/spec/integration/util/autoload_spec.rb @@ -1,102 +1,102 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/autoload' require 'fileutils' class AutoloadIntegrator @things = [] def self.newthing(name) @things << name end def self.thing?(name) @things.include? name end def self.clear @things.clear end end require 'puppet_spec/files' describe Puppet::Util::Autoload do include PuppetSpec::Files def with_file(name, *path) path = File.join(*path) # Now create a file to load File.open(path, "w") { |f| f.puts "\nAutoloadIntegrator.newthing(:#{name.to_s})\n" } yield File.delete(path) end def with_loader(name, path) dir = tmpfile(name + path) $LOAD_PATH << dir Dir.mkdir(dir) rbdir = File.join(dir, path.to_s) Dir.mkdir(rbdir) loader = Puppet::Util::Autoload.new(name, path) yield rbdir, loader Dir.rmdir(rbdir) Dir.rmdir(dir) $LOAD_PATH.pop AutoloadIntegrator.clear end it "should not fail when asked to load a missing file" do - Puppet::Util::Autoload.new("foo", "bar").load(:eh).should be_false + Puppet::Util::Autoload.new("foo", "bar").load(:eh).should be_falsey end it "should load and return true when it successfully loads a file" do with_loader("foo", "bar") { |dir,loader| with_file(:mything, dir, "mything.rb") { - loader.load(:mything).should be_true + loader.load(:mything).should be_truthy loader.class.should be_loaded("bar/mything") AutoloadIntegrator.should be_thing(:mything) } } end it "should consider a file loaded when asked for the name without an extension" do with_loader("foo", "bar") { |dir,loader| with_file(:noext, dir, "noext.rb") { loader.load(:noext) loader.class.should be_loaded("bar/noext") } } end it "should consider a file loaded when asked for the name with an extension" do with_loader("foo", "bar") { |dir,loader| with_file(:noext, dir, "withext.rb") { loader.load(:withext) loader.class.should be_loaded("bar/withext.rb") } } end it "should be able to load files directly from modules" do ## modulepath can't be used until after app settings are initialized, so we need to simulate that: Puppet.settings.expects(:app_defaults_initialized?).returns(true).at_least_once modulepath = tmpfile("autoload_module_testing") libdir = File.join(modulepath, "mymod", "lib", "foo") FileUtils.mkdir_p(libdir) file = File.join(libdir, "plugin.rb") Puppet.override(:environments => Puppet::Environments::Static.new(Puppet::Node::Environment.create(:production, [modulepath]))) do with_loader("foo", "foo") do |dir, loader| with_file(:plugin, file.split("/")) do loader.load(:plugin) loader.class.should be_loaded("foo/plugin.rb") end end end end end diff --git a/spec/integration/util/rdoc/parser_spec.rb b/spec/integration/util/rdoc/parser_spec.rb index d6a9094de..1bd5e1f20 100755 --- a/spec/integration/util/rdoc/parser_spec.rb +++ b/spec/integration/util/rdoc/parser_spec.rb @@ -1,182 +1,182 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/rdoc' describe "RDoc::Parser" do require 'puppet_spec/files' include PuppetSpec::Files let(:document_all) { false } let(:tmp_dir) { tmpdir('rdoc_parser_tmp') } let(:doc_dir) { File.join(tmp_dir, 'doc') } let(:manifests_dir) { File.join(tmp_dir, 'manifests') } let(:modules_dir) { File.join(tmp_dir, 'modules') } let(:modules_and_manifests) do { :site => [ File.join(manifests_dir, 'site.pp'), <<-EOF # The test class comment class test { # The virtual resource comment @notify { virtual: } # The a_notify_resource comment notify { a_notify_resource: message => "a_notify_resource message" } } # The includes_another class comment class includes_another { include another } # The requires_another class comment class requires_another { require another } # node comment node foo { include test $a_var = "var_value" realize Notify[virtual] notify { bar: } } EOF ], :module_readme => [ File.join(modules_dir, 'a_module', 'README'), <<-EOF The a_module README docs. EOF ], :module_init => [ File.join(modules_dir, 'a_module', 'manifests', 'init.pp'), <<-EOF # The a_module class comment class a_module {} class another {} EOF ], :module_type => [ File.join(modules_dir, 'a_module', 'manifests', 'a_type.pp'), <<-EOF # The a_type type comment define a_module::a_type() {} EOF ], :module_plugin => [ File.join(modules_dir, 'a_module', 'lib', 'puppet', 'type', 'a_plugin.rb'), <<-EOF # The a_plugin type comment Puppet::Type.newtype(:a_plugin) do @doc = "Not presented" end EOF ], :module_function => [ File.join(modules_dir, 'a_module', 'lib', 'puppet', 'parser', 'a_function.rb'), <<-EOF # The a_function function comment module Puppet::Parser::Functions newfunction(:a_function, :type => :rvalue) do return end end EOF ], :module_fact => [ File.join(modules_dir, 'a_module', 'lib', 'facter', 'a_fact.rb'), <<-EOF # The a_fact fact comment Facter.add("a_fact") do end EOF ], } end def write_file(file, content) FileUtils.mkdir_p(File.dirname(file)) File.open(file, 'w') do |f| f.puts(content) end end def prepare_manifests_and_modules modules_and_manifests.each do |key,array| write_file(*array) end end def file_exists_and_matches_content(file, *content_patterns) - Puppet::FileSystem.exist?(file).should(be_true, "Cannot find #{file}") + Puppet::FileSystem.exist?(file).should(be_truthy, "Cannot find #{file}") content_patterns.each do |pattern| content = File.read(file) content.should match(pattern) end end def some_file_exists_with_matching_content(glob, *content_patterns) Dir.glob(glob).select do |f| contents = File.read(f) content_patterns.all? { |p| p.match(contents) } end.should_not(be_empty, "Could not match #{content_patterns} in any of the files found in #{glob}") end around(:each) do |example| env = Puppet::Node::Environment.create(:doc_test_env, [modules_dir], manifests_dir) Puppet.override({:environments => Puppet::Environments::Static.new(env), :current_environment => env}) do example.run end end before :each do prepare_manifests_and_modules Puppet.settings[:document_all] = document_all Puppet.settings[:modulepath] = modules_dir Puppet::Util::RDoc.rdoc(doc_dir, [modules_dir, manifests_dir]) end module RdocTesters def has_plugin_rdoc(module_name, type, name) file_exists_and_matches_content(plugin_path(module_name, type, name), /The .*?#{name}.*?\s*#{type} comment/m, /Type.*?#{type}/m) end end shared_examples_for :an_rdoc_site do # PUP-3274 / PUP-3638 not sure if this should be kept or not - it is now broken # it "documents the __site__ module" do # has_module_rdoc("__site__") # end # PUP-3274 / PUP-3638 not sure if this should be kept or not - it is now broken # it "documents the a_module module" do # has_module_rdoc("a_module", /The .*?a_module.*? .*?README.*?docs/m) # end it "documents the a_module::a_plugin type" do has_plugin_rdoc("a_module", :type, 'a_plugin') end it "documents the a_module::a_function function" do has_plugin_rdoc("a_module", :function, 'a_function') end it "documents the a_module::a_fact fact" do has_plugin_rdoc("a_module", :fact, 'a_fact') end end describe "rdoc2 support" do def module_path(module_name); "#{doc_dir}/#{module_name}.html" end def plugin_path(module_name, type, name); "#{doc_dir}/#{module_name}/__#{type}s__.html" end include RdocTesters it_behaves_like :an_rdoc_site end end diff --git a/spec/integration/util/settings_spec.rb b/spec/integration/util/settings_spec.rb index a76ade637..e97fd9745 100755 --- a/spec/integration/util/settings_spec.rb +++ b/spec/integration/util/settings_spec.rb @@ -1,89 +1,89 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/files' describe Puppet::Settings do include PuppetSpec::Files def minimal_default_settings { :noop => {:default => false, :desc => "noop"} } end def define_settings(section, settings_hash) settings.define_settings(section, minimal_default_settings.update(settings_hash)) end let(:settings) { Puppet::Settings.new } it "should be able to make needed directories" do define_settings(:main, :maindir => { :default => tmpfile("main"), :type => :directory, :desc => "a", } ) settings.use(:main) - expect(File.directory?(settings[:maindir])).to be_true + expect(File.directory?(settings[:maindir])).to be_truthy end it "should make its directories with the correct modes" do define_settings(:main, :maindir => { :default => tmpfile("main"), :type => :directory, :desc => "a", :mode => 0750 } ) settings.use(:main) expect(Puppet::FileSystem.stat(settings[:maindir]).mode & 007777).to eq(0750) end it "reparses configuration if configuration file is touched", :if => !Puppet.features.microsoft_windows? do config = tmpfile("config") define_settings(:main, :config => { :type => :file, :default => config, :desc => "a" }, :environment => { :default => 'dingos', :desc => 'test', } ) Puppet[:filetimeout] = '1s' File.open(config, 'w') do |file| file.puts <<-EOF [main] environment=toast EOF end settings.initialize_global_settings expect(settings[:environment]).to eq('toast') # First reparse establishes WatchedFiles settings.reparse_config_files sleep 1 File.open(config, 'w') do |file| file.puts <<-EOF [main] environment=bacon EOF end # Second reparse if later than filetimeout, reparses if changed settings.reparse_config_files expect(settings[:environment]).to eq('bacon') end end diff --git a/spec/integration/util/windows/process_spec.rb b/spec/integration/util/windows/process_spec.rb index 60eae3443..5d6c88e84 100644 --- a/spec/integration/util/windows/process_spec.rb +++ b/spec/integration/util/windows/process_spec.rb @@ -1,34 +1,34 @@ #! /usr/bin/env ruby require 'spec_helper' require 'facter' describe "Puppet::Util::Windows::Process", :if => Puppet.features.microsoft_windows? do describe "as an admin" do it "should have the SeCreateSymbolicLinkPrivilege necessary to create symlinks on Vista / 2008+", :if => Facter.value(:kernelmajversion).to_f >= 6.0 && Puppet.features.microsoft_windows? do # this is a bit of a lame duck test since it requires running user to be admin # a better integration test would create a new user with the privilege and verify Puppet::Util::Windows::User.should be_admin - Puppet::Util::Windows::Process.process_privilege_symlink?.should be_true + Puppet::Util::Windows::Process.process_privilege_symlink?.should be_truthy end it "should not have the SeCreateSymbolicLinkPrivilege necessary to create symlinks on 2003 and earlier", :if => Facter.value(:kernelmajversion).to_f < 6.0 && Puppet.features.microsoft_windows? do Puppet::Util::Windows::User.should be_admin - Puppet::Util::Windows::Process.process_privilege_symlink?.should be_false + Puppet::Util::Windows::Process.process_privilege_symlink?.should be_falsey end it "should be able to lookup a standard Windows process privilege" do Puppet::Util::Windows::Process.lookup_privilege_value('SeShutdownPrivilege') do |luid| luid.should_not be_nil luid.should be_instance_of(Puppet::Util::Windows::Process::LUID) end end it "should raise an error for an unknown privilege name" do fail_msg = /LookupPrivilegeValue\(, foo, .*\): A specified privilege does not exist/ expect { Puppet::Util::Windows::Process.lookup_privilege_value('foo') }.to raise_error(Puppet::Util::Windows::Error, fail_msg) end end end diff --git a/spec/integration/util/windows/security_spec.rb b/spec/integration/util/windows/security_spec.rb index fa3873aee..2d098ada6 100755 --- a/spec/integration/util/windows/security_spec.rb +++ b/spec/integration/util/windows/security_spec.rb @@ -1,864 +1,864 @@ #!/usr/bin/env ruby require 'spec_helper' if Puppet.features.microsoft_windows? class WindowsSecurityTester require 'puppet/util/windows/security' include Puppet::Util::Windows::Security end end describe "Puppet::Util::Windows::Security", :if => Puppet.features.microsoft_windows? do include PuppetSpec::Files before :all do @sids = { :current_user => Puppet::Util::Windows::SID.name_to_sid(Puppet::Util::Windows::ADSI::User.current_user_name), :system => Win32::Security::SID::LocalSystem, :admin => Puppet::Util::Windows::SID.name_to_sid("Administrator"), :administrators => Win32::Security::SID::BuiltinAdministrators, :guest => Puppet::Util::Windows::SID.name_to_sid("Guest"), :users => Win32::Security::SID::BuiltinUsers, :power_users => Win32::Security::SID::PowerUsers, :none => Win32::Security::SID::Nobody, :everyone => Win32::Security::SID::Everyone } # The TCP/IP NetBIOS Helper service (aka 'lmhosts') has ended up # disabled on some VMs for reasons we couldn't track down. This # condition causes tests which rely on resolving UNC style paths # (like \\localhost) to fail with unhelpful error messages. # Put a check for this upfront to aid debug should this strike again. service = Puppet::Type.type(:service).new(:name => 'lmhosts') expect(service.provider.status).to eq(:running), 'lmhosts service is not running' end let (:sids) { @sids } let (:winsec) { WindowsSecurityTester.new } let (:klass) { Puppet::Util::Windows::File } def set_group_depending_on_current_user(path) if sids[:current_user] == sids[:system] # if the current user is SYSTEM, by setting the group to # guest, SYSTEM is automagically given full control, so instead # override that behavior with SYSTEM as group and a specific mode winsec.set_group(sids[:system], path) mode = winsec.get_mode(path) winsec.set_mode(mode & ~WindowsSecurityTester::S_IRWXG, path) else winsec.set_group(sids[:guest], path) end end def grant_everyone_full_access(path) sd = winsec.get_security_descriptor(path) everyone = 'S-1-1-0' inherit = Puppet::Util::Windows::AccessControlEntry::OBJECT_INHERIT_ACE | Puppet::Util::Windows::AccessControlEntry::CONTAINER_INHERIT_ACE sd.dacl.allow(everyone, klass::FILE_ALL_ACCESS, inherit) winsec.set_security_descriptor(path, sd) end shared_examples_for "only child owner" do it "should allow child owner" do winsec.set_owner(sids[:guest], parent) winsec.set_group(sids[:current_user], parent) winsec.set_mode(0700, parent) check_delete(path) end it "should deny parent owner" do winsec.set_owner(sids[:guest], path) winsec.set_group(sids[:current_user], path) winsec.set_mode(0700, path) lambda { check_delete(path) }.should raise_error(Errno::EACCES) end it "should deny group" do winsec.set_owner(sids[:guest], path) winsec.set_group(sids[:current_user], path) winsec.set_mode(0700, path) lambda { check_delete(path) }.should raise_error(Errno::EACCES) end it "should deny other" do winsec.set_owner(sids[:guest], path) winsec.set_group(sids[:current_user], path) winsec.set_mode(0700, path) lambda { check_delete(path) }.should raise_error(Errno::EACCES) end end shared_examples_for "a securable object" do describe "on a volume that doesn't support ACLs" do [:owner, :group, :mode].each do |p| it "should return nil #{p}" do winsec.stubs(:supports_acl?).returns false winsec.send("get_#{p}", path).should be_nil end end end describe "on a volume that supports ACLs" do describe "for a normal user" do before :each do Puppet.features.stubs(:root?).returns(false) end after :each do winsec.set_mode(WindowsSecurityTester::S_IRWXU, parent) winsec.set_mode(WindowsSecurityTester::S_IRWXU, path) if Puppet::FileSystem.exist?(path) end describe "#supports_acl?" do %w[c:/ c:\\ c:/windows/system32 \\\\localhost\\C$ \\\\127.0.0.1\\C$\\foo].each do |path| it "should accept #{path}" do winsec.should be_supports_acl(path) end end it "should raise an exception if it cannot get volume information" do expect { winsec.supports_acl?('foobar') }.to raise_error(Puppet::Error, /Failed to get volume information/) end end describe "#owner=" do it "should allow setting to the current user" do winsec.set_owner(sids[:current_user], path) end it "should raise an exception when setting to a different user" do lambda { winsec.set_owner(sids[:guest], path) }.should raise_error(Puppet::Error, /This security ID may not be assigned as the owner of this object./) end end describe "#owner" do it "it should not be empty" do winsec.get_owner(path).should_not be_empty end it "should raise an exception if an invalid path is provided" do lambda { winsec.get_owner("c:\\doesnotexist.txt") }.should raise_error(Puppet::Error, /The system cannot find the file specified./) end end describe "#group=" do it "should allow setting to a group the current owner is a member of" do winsec.set_group(sids[:users], path) end # Unlike unix, if the user has permission to WRITE_OWNER, which the file owner has by default, # then they can set the primary group to a group that the user does not belong to. it "should allow setting to a group the current owner is not a member of" do winsec.set_group(sids[:power_users], path) end end describe "#group" do it "should not be empty" do winsec.get_group(path).should_not be_empty end it "should raise an exception if an invalid path is provided" do lambda { winsec.get_group("c:\\doesnotexist.txt") }.should raise_error(Puppet::Error, /The system cannot find the file specified./) end end it "should preserve inherited full control for SYSTEM when setting owner and group" do # new file has SYSTEM system_aces = winsec.get_aces_for_path_by_sid(path, sids[:system]) system_aces.should_not be_empty # when running under SYSTEM account, multiple ACEs come back # so we only care that we have at least one of these system_aces.any? do |ace| ace.mask == klass::FILE_ALL_ACCESS - end.should be_true + end.should be_truthy # changing the owner/group will no longer make the SD protected winsec.set_group(sids[:power_users], path) winsec.set_owner(sids[:administrators], path) system_aces.find do |ace| ace.mask == klass::FILE_ALL_ACCESS && ace.inherited? end.should_not be_nil end describe "#mode=" do (0000..0700).step(0100) do |mode| it "should enforce mode #{mode.to_s(8)}" do winsec.set_mode(mode, path) check_access(mode, path) end end it "should round-trip all 128 modes that do not require deny ACEs" do 0.upto(1).each do |s| 0.upto(7).each do |u| 0.upto(u).each do |g| 0.upto(g).each do |o| # if user is superset of group, and group superset of other, then # no deny ace is required, and mode can be converted to win32 # access mask, and back to mode without loss of information # (provided the owner and group are not the same) next if ((u & g) != g) or ((g & o) != o) mode = (s << 9 | u << 6 | g << 3 | o << 0) winsec.set_mode(mode, path) winsec.get_mode(path).to_s(8).should == mode.to_s(8) end end end end end it "should preserve full control for SYSTEM when setting mode" do # new file has SYSTEM system_aces = winsec.get_aces_for_path_by_sid(path, sids[:system]) system_aces.should_not be_empty # when running under SYSTEM account, multiple ACEs come back # so we only care that we have at least one of these system_aces.any? do |ace| ace.mask == klass::FILE_ALL_ACCESS - end.should be_true + end.should be_truthy # changing the mode will make the SD protected winsec.set_group(sids[:none], path) winsec.set_mode(0600, path) # and should have a non-inherited SYSTEM ACE(s) system_aces = winsec.get_aces_for_path_by_sid(path, sids[:system]) system_aces.each do |ace| ace.mask.should == klass::FILE_ALL_ACCESS && ! ace.inherited? end end describe "for modes that require deny aces" do it "should map everyone to group and owner" do winsec.set_mode(0426, path) winsec.get_mode(path).to_s(8).should == "666" end it "should combine user and group modes when owner and group sids are equal" do winsec.set_group(winsec.get_owner(path), path) winsec.set_mode(0410, path) winsec.get_mode(path).to_s(8).should == "550" end end describe "for read-only objects" do before :each do winsec.set_group(sids[:none], path) winsec.set_mode(0600, path) Puppet::Util::Windows::File.add_attributes(path, klass::FILE_ATTRIBUTE_READONLY) (Puppet::Util::Windows::File.get_attributes(path) & klass::FILE_ATTRIBUTE_READONLY).should be_nonzero end it "should make them writable if any sid has write permission" do winsec.set_mode(WindowsSecurityTester::S_IWUSR, path) (Puppet::Util::Windows::File.get_attributes(path) & klass::FILE_ATTRIBUTE_READONLY).should == 0 end it "should leave them read-only if no sid has write permission and should allow full access for SYSTEM" do winsec.set_mode(WindowsSecurityTester::S_IRUSR | WindowsSecurityTester::S_IXGRP, path) (Puppet::Util::Windows::File.get_attributes(path) & klass::FILE_ATTRIBUTE_READONLY).should be_nonzero system_aces = winsec.get_aces_for_path_by_sid(path, sids[:system]) # when running under SYSTEM account, and set_group / set_owner hasn't been called # SYSTEM full access will be restored system_aces.any? do |ace| ace.mask == klass::FILE_ALL_ACCESS - end.should be_true + end.should be_truthy end end it "should raise an exception if an invalid path is provided" do lambda { winsec.set_mode(sids[:guest], "c:\\doesnotexist.txt") }.should raise_error(Puppet::Error, /The system cannot find the file specified./) end end describe "#mode" do it "should report when extra aces are encounted" do sd = winsec.get_security_descriptor(path) (544..547).each do |rid| sd.dacl.allow("S-1-5-32-#{rid}", klass::STANDARD_RIGHTS_ALL) end winsec.set_security_descriptor(path, sd) mode = winsec.get_mode(path) (mode & WindowsSecurityTester::S_IEXTRA).should == WindowsSecurityTester::S_IEXTRA end it "should return deny aces" do sd = winsec.get_security_descriptor(path) sd.dacl.deny(sids[:guest], klass::FILE_GENERIC_WRITE) winsec.set_security_descriptor(path, sd) guest_aces = winsec.get_aces_for_path_by_sid(path, sids[:guest]) guest_aces.find do |ace| ace.type == Puppet::Util::Windows::AccessControlEntry::ACCESS_DENIED_ACE_TYPE end.should_not be_nil end it "should skip inherit-only ace" do sd = winsec.get_security_descriptor(path) dacl = Puppet::Util::Windows::AccessControlList.new dacl.allow( sids[:current_user], klass::STANDARD_RIGHTS_ALL | klass::SPECIFIC_RIGHTS_ALL ) dacl.allow( sids[:everyone], klass::FILE_GENERIC_READ, Puppet::Util::Windows::AccessControlEntry::INHERIT_ONLY_ACE | Puppet::Util::Windows::AccessControlEntry::OBJECT_INHERIT_ACE ) winsec.set_security_descriptor(path, sd) (winsec.get_mode(path) & WindowsSecurityTester::S_IRWXO).should == 0 end it "should raise an exception if an invalid path is provided" do lambda { winsec.get_mode("c:\\doesnotexist.txt") }.should raise_error(Puppet::Error, /The system cannot find the file specified./) end end describe "inherited access control entries" do it "should be absent when the access control list is protected, and should not remove SYSTEM" do winsec.set_mode(WindowsSecurityTester::S_IRWXU, path) mode = winsec.get_mode(path) [ WindowsSecurityTester::S_IEXTRA, WindowsSecurityTester::S_ISYSTEM_MISSING ].each do |flag| (mode & flag).should_not == flag end end it "should be present when the access control list is unprotected" do # add a bunch of aces to the parent with permission to add children allow = klass::STANDARD_RIGHTS_ALL | klass::SPECIFIC_RIGHTS_ALL inherit = Puppet::Util::Windows::AccessControlEntry::OBJECT_INHERIT_ACE | Puppet::Util::Windows::AccessControlEntry::CONTAINER_INHERIT_ACE sd = winsec.get_security_descriptor(parent) sd.dacl.allow( "S-1-1-0", #everyone allow, inherit ) (544..547).each do |rid| sd.dacl.allow( "S-1-5-32-#{rid}", klass::STANDARD_RIGHTS_ALL, inherit ) end winsec.set_security_descriptor(parent, sd) # unprotect child, it should inherit from parent winsec.set_mode(WindowsSecurityTester::S_IRWXU, path, false) (winsec.get_mode(path) & WindowsSecurityTester::S_IEXTRA).should == WindowsSecurityTester::S_IEXTRA end end end describe "for an administrator", :if => (Puppet.features.root? && Puppet.features.microsoft_windows?) do before :each do is_dir = Puppet::FileSystem.directory?(path) winsec.set_mode(WindowsSecurityTester::S_IRWXU | WindowsSecurityTester::S_IRWXG, path) set_group_depending_on_current_user(path) winsec.set_owner(sids[:guest], path) expected_error = RUBY_VERSION =~ /^2\./ && is_dir ? Errno::EISDIR : Errno::EACCES lambda { File.open(path, 'r') }.should raise_error(expected_error) end after :each do if Puppet::FileSystem.exist?(path) winsec.set_owner(sids[:current_user], path) winsec.set_mode(WindowsSecurityTester::S_IRWXU, path) end end describe "#owner=" do it "should accept a user sid" do winsec.set_owner(sids[:admin], path) winsec.get_owner(path).should == sids[:admin] end it "should accept a group sid" do winsec.set_owner(sids[:power_users], path) winsec.get_owner(path).should == sids[:power_users] end it "should raise an exception if an invalid sid is provided" do lambda { winsec.set_owner("foobar", path) }.should raise_error(Puppet::Error, /Failed to convert string SID/) end it "should raise an exception if an invalid path is provided" do lambda { winsec.set_owner(sids[:guest], "c:\\doesnotexist.txt") }.should raise_error(Puppet::Error, /The system cannot find the file specified./) end end describe "#group=" do it "should accept a group sid" do winsec.set_group(sids[:power_users], path) winsec.get_group(path).should == sids[:power_users] end it "should accept a user sid" do winsec.set_group(sids[:admin], path) winsec.get_group(path).should == sids[:admin] end it "should combine owner and group rights when they are the same sid" do winsec.set_owner(sids[:power_users], path) winsec.set_group(sids[:power_users], path) winsec.set_mode(0610, path) winsec.get_owner(path).should == sids[:power_users] winsec.get_group(path).should == sids[:power_users] # note group execute permission added to user ace, and then group rwx value # reflected to match # Exclude missing system ace, since that's not relevant (winsec.get_mode(path) & 0777).to_s(8).should == "770" end it "should raise an exception if an invalid sid is provided" do lambda { winsec.set_group("foobar", path) }.should raise_error(Puppet::Error, /Failed to convert string SID/) end it "should raise an exception if an invalid path is provided" do lambda { winsec.set_group(sids[:guest], "c:\\doesnotexist.txt") }.should raise_error(Puppet::Error, /The system cannot find the file specified./) end end describe "when the sid is NULL" do it "should retrieve an empty owner sid" it "should retrieve an empty group sid" end describe "when the sid refers to a deleted trustee" do it "should retrieve the user sid" do sid = nil user = Puppet::Util::Windows::ADSI::User.create("puppet#{rand(10000)}") user.commit begin sid = Puppet::Util::Windows::ADSI::User.new(user.name).sid.to_s winsec.set_owner(sid, path) winsec.set_mode(WindowsSecurityTester::S_IRWXU, path) ensure Puppet::Util::Windows::ADSI::User.delete(user.name) end winsec.get_owner(path).should == sid winsec.get_mode(path).should == WindowsSecurityTester::S_IRWXU end it "should retrieve the group sid" do sid = nil group = Puppet::Util::Windows::ADSI::Group.create("puppet#{rand(10000)}") group.commit begin sid = Puppet::Util::Windows::ADSI::Group.new(group.name).sid.to_s winsec.set_group(sid, path) winsec.set_mode(WindowsSecurityTester::S_IRWXG, path) ensure Puppet::Util::Windows::ADSI::Group.delete(group.name) end winsec.get_group(path).should == sid winsec.get_mode(path).should == WindowsSecurityTester::S_IRWXG end end describe "#mode" do it "should deny all access when the DACL is empty, including SYSTEM" do sd = winsec.get_security_descriptor(path) # don't allow inherited aces to affect the test protect = true new_sd = Puppet::Util::Windows::SecurityDescriptor.new(sd.owner, sd.group, [], protect) winsec.set_security_descriptor(path, new_sd) winsec.get_mode(path).should == WindowsSecurityTester::S_ISYSTEM_MISSING end # REMIND: ruby crashes when trying to set a NULL DACL # it "should allow all when it is nil" do # winsec.set_owner(sids[:current_user], path) # winsec.open_file(path, WindowsSecurityTester::READ_CONTROL | WindowsSecurityTester::WRITE_DAC) do |handle| # winsec.set_security_info(handle, WindowsSecurityTester::DACL_SECURITY_INFORMATION | WindowsSecurityTester::PROTECTED_DACL_SECURITY_INFORMATION, nil) # end # winsec.get_mode(path).to_s(8).should == "777" # end end describe "when the parent directory" do before :each do winsec.set_owner(sids[:current_user], parent) winsec.set_owner(sids[:current_user], path) winsec.set_mode(0777, path, false) end describe "is writable and executable" do describe "and sticky bit is set" do it "should allow child owner" do winsec.set_owner(sids[:guest], parent) winsec.set_group(sids[:current_user], parent) winsec.set_mode(01700, parent) check_delete(path) end it "should allow parent owner" do winsec.set_owner(sids[:current_user], parent) winsec.set_group(sids[:guest], parent) winsec.set_mode(01700, parent) winsec.set_owner(sids[:current_user], path) winsec.set_group(sids[:guest], path) winsec.set_mode(0700, path) check_delete(path) end it "should deny group" do winsec.set_owner(sids[:guest], parent) winsec.set_group(sids[:current_user], parent) winsec.set_mode(01770, parent) winsec.set_owner(sids[:guest], path) winsec.set_group(sids[:current_user], path) winsec.set_mode(0700, path) lambda { check_delete(path) }.should raise_error(Errno::EACCES) end it "should deny other" do winsec.set_owner(sids[:guest], parent) winsec.set_group(sids[:current_user], parent) winsec.set_mode(01777, parent) winsec.set_owner(sids[:guest], path) winsec.set_group(sids[:current_user], path) winsec.set_mode(0700, path) lambda { check_delete(path) }.should raise_error(Errno::EACCES) end end describe "and sticky bit is not set" do it "should allow child owner" do winsec.set_owner(sids[:guest], parent) winsec.set_group(sids[:current_user], parent) winsec.set_mode(0700, parent) check_delete(path) end it "should allow parent owner" do winsec.set_owner(sids[:current_user], parent) winsec.set_group(sids[:guest], parent) winsec.set_mode(0700, parent) winsec.set_owner(sids[:current_user], path) winsec.set_group(sids[:guest], path) winsec.set_mode(0700, path) check_delete(path) end it "should allow group" do winsec.set_owner(sids[:guest], parent) winsec.set_group(sids[:current_user], parent) winsec.set_mode(0770, parent) winsec.set_owner(sids[:guest], path) winsec.set_group(sids[:current_user], path) winsec.set_mode(0700, path) check_delete(path) end it "should allow other" do winsec.set_owner(sids[:guest], parent) winsec.set_group(sids[:current_user], parent) winsec.set_mode(0777, parent) winsec.set_owner(sids[:guest], path) winsec.set_group(sids[:current_user], path) winsec.set_mode(0700, path) check_delete(path) end end end describe "is not writable" do before :each do winsec.set_group(sids[:current_user], parent) winsec.set_mode(0555, parent) end it_behaves_like "only child owner" end describe "is not executable" do before :each do winsec.set_group(sids[:current_user], parent) winsec.set_mode(0666, parent) end it_behaves_like "only child owner" end end end end end describe "file" do let (:parent) do tmpdir('win_sec_test_file') end let (:path) do path = File.join(parent, 'childfile') File.new(path, 'w').close path end after :each do # allow temp files to be cleaned up grant_everyone_full_access(parent) end it_behaves_like "a securable object" do def check_access(mode, path) if (mode & WindowsSecurityTester::S_IRUSR).nonzero? check_read(path) else lambda { check_read(path) }.should raise_error(Errno::EACCES) end if (mode & WindowsSecurityTester::S_IWUSR).nonzero? check_write(path) else lambda { check_write(path) }.should raise_error(Errno::EACCES) end if (mode & WindowsSecurityTester::S_IXUSR).nonzero? lambda { check_execute(path) }.should raise_error(Errno::ENOEXEC) else lambda { check_execute(path) }.should raise_error(Errno::EACCES) end end def check_read(path) File.open(path, 'r').close end def check_write(path) File.open(path, 'w').close end def check_execute(path) Kernel.exec(path) end def check_delete(path) File.delete(path) end end describe "locked files" do let (:explorer) { File.join(Dir::WINDOWS, "explorer.exe") } it "should get the owner" do winsec.get_owner(explorer).should match /^S-1-5-/ end it "should get the group" do winsec.get_group(explorer).should match /^S-1-5-/ end it "should get the mode" do winsec.get_mode(explorer).should == (WindowsSecurityTester::S_IRWXU | WindowsSecurityTester::S_IRWXG | WindowsSecurityTester::S_IEXTRA) end end end describe "directory" do let (:parent) do tmpdir('win_sec_test_dir') end let (:path) do path = File.join(parent, 'childdir') Dir.mkdir(path) path end after :each do # allow temp files to be cleaned up grant_everyone_full_access(parent) end it_behaves_like "a securable object" do def check_access(mode, path) if (mode & WindowsSecurityTester::S_IRUSR).nonzero? check_read(path) else lambda { check_read(path) }.should raise_error(Errno::EACCES) end if (mode & WindowsSecurityTester::S_IWUSR).nonzero? check_write(path) else lambda { check_write(path) }.should raise_error(Errno::EACCES) end if (mode & WindowsSecurityTester::S_IXUSR).nonzero? check_execute(path) else lambda { check_execute(path) }.should raise_error(Errno::EACCES) end end def check_read(path) Dir.entries(path) end def check_write(path) Dir.mkdir(File.join(path, "subdir")) end def check_execute(path) Dir.chdir(path) {} end def check_delete(path) Dir.rmdir(path) end end describe "inheritable aces" do it "should be applied to child objects" do mode640 = WindowsSecurityTester::S_IRUSR | WindowsSecurityTester::S_IWUSR | WindowsSecurityTester::S_IRGRP winsec.set_mode(mode640, path) newfile = File.join(path, "newfile.txt") File.new(newfile, "w").close newdir = File.join(path, "newdir") Dir.mkdir(newdir) [newfile, newdir].each do |p| mode = winsec.get_mode(p) (mode & 07777).to_s(8).should == mode640.to_s(8) end end end end context "security descriptor" do let(:path) { tmpfile('sec_descriptor') } let(:read_execute) { 0x201FF } let(:synchronize) { 0x100000 } before :each do FileUtils.touch(path) end it "preserves aces for other users" do dacl = Puppet::Util::Windows::AccessControlList.new sids_in_dacl = [sids[:current_user], sids[:users]] sids_in_dacl.each do |sid| dacl.allow(sid, read_execute) end sd = Puppet::Util::Windows::SecurityDescriptor.new(sids[:guest], sids[:guest], dacl, true) winsec.set_security_descriptor(path, sd) aces = winsec.get_security_descriptor(path).dacl.to_a aces.map(&:sid).should == sids_in_dacl - aces.map(&:mask).all? { |mask| mask == read_execute }.should be_true + aces.map(&:mask).all? { |mask| mask == read_execute }.should be_truthy end it "changes the sid for all aces that were assigned to the old owner" do sd = winsec.get_security_descriptor(path) sd.owner.should_not == sids[:guest] sd.dacl.allow(sd.owner, read_execute) sd.dacl.allow(sd.owner, synchronize) sd.owner = sids[:guest] winsec.set_security_descriptor(path, sd) dacl = winsec.get_security_descriptor(path).dacl aces = dacl.find_all { |ace| ace.sid == sids[:guest] } # only non-inherited aces will be reassigned to guest, so # make sure we find at least the two we added aces.size.should >= 2 end it "preserves INHERIT_ONLY_ACEs" do # inherit only aces can only be set on directories dir = tmpdir('inheritonlyace') inherit_flags = Puppet::Util::Windows::AccessControlEntry::INHERIT_ONLY_ACE | Puppet::Util::Windows::AccessControlEntry::OBJECT_INHERIT_ACE | Puppet::Util::Windows::AccessControlEntry::CONTAINER_INHERIT_ACE sd = winsec.get_security_descriptor(dir) sd.dacl.allow(sd.owner, klass::FILE_ALL_ACCESS, inherit_flags) winsec.set_security_descriptor(dir, sd) sd = winsec.get_security_descriptor(dir) winsec.set_owner(sids[:guest], dir) sd = winsec.get_security_descriptor(dir) sd.dacl.find do |ace| ace.sid == sids[:guest] && ace.inherit_only? end.should_not be_nil end it "allows deny ACEs with inheritance" do # inheritance can only be set on directories dir = tmpdir('denyaces') inherit_flags = Puppet::Util::Windows::AccessControlEntry::OBJECT_INHERIT_ACE | Puppet::Util::Windows::AccessControlEntry::CONTAINER_INHERIT_ACE sd = winsec.get_security_descriptor(dir) sd.dacl.deny(sids[:guest], klass::FILE_ALL_ACCESS, inherit_flags) winsec.set_security_descriptor(dir, sd) sd = winsec.get_security_descriptor(dir) sd.dacl.find do |ace| ace.sid == sids[:guest] && ace.flags != 0 end.should_not be_nil end context "when managing mode" do it "removes aces for sids that are neither the owner nor group" do # add a guest ace, it's never owner or group sd = winsec.get_security_descriptor(path) sd.dacl.allow(sids[:guest], read_execute) winsec.set_security_descriptor(path, sd) # setting the mode, it should remove extra aces winsec.set_mode(0770, path) # make sure it's gone dacl = winsec.get_security_descriptor(path).dacl aces = dacl.find_all { |ace| ace.sid == sids[:guest] } aces.should be_empty end end end end diff --git a/spec/integration/util/windows/user_spec.rb b/spec/integration/util/windows/user_spec.rb index 4e873b34c..93b0f09b7 100755 --- a/spec/integration/util/windows/user_spec.rb +++ b/spec/integration/util/windows/user_spec.rb @@ -1,125 +1,125 @@ #! /usr/bin/env ruby require 'spec_helper' describe "Puppet::Util::Windows::User", :if => Puppet.features.microsoft_windows? do describe "2003 without UAC" do before :each do Facter.stubs(:value).with(:kernelmajversion).returns("5.2") end it "should be an admin if user's token contains the Administrators SID" do Puppet::Util::Windows::User.expects(:check_token_membership).returns(true) Puppet::Util::Windows::Process.expects(:elevated_security?).never Puppet::Util::Windows::User.should be_admin end it "should not be an admin if user's token doesn't contain the Administrators SID" do Puppet::Util::Windows::User.expects(:check_token_membership).returns(false) Puppet::Util::Windows::Process.expects(:elevated_security?).never Puppet::Util::Windows::User.should_not be_admin end it "should raise an exception if we can't check token membership" do Puppet::Util::Windows::User.expects(:check_token_membership).raises(Puppet::Util::Windows::Error, "Access denied.") Puppet::Util::Windows::Process.expects(:elevated_security?).never lambda { Puppet::Util::Windows::User.admin? }.should raise_error(Puppet::Util::Windows::Error, /Access denied./) end end describe "2008 with UAC" do before :each do Facter.stubs(:value).with(:kernelmajversion).returns("6.0") end it "should be an admin if user is running with elevated privileges" do Puppet::Util::Windows::Process.stubs(:elevated_security?).returns(true) Puppet::Util::Windows::User.expects(:check_token_membership).never Puppet::Util::Windows::User.should be_admin end it "should not be an admin if user is not running with elevated privileges" do Puppet::Util::Windows::Process.stubs(:elevated_security?).returns(false) Puppet::Util::Windows::User.expects(:check_token_membership).never Puppet::Util::Windows::User.should_not be_admin end it "should raise an exception if the process fails to open the process token" do Puppet::Util::Windows::Process.stubs(:elevated_security?).raises(Puppet::Util::Windows::Error, "Access denied.") Puppet::Util::Windows::User.expects(:check_token_membership).never lambda { Puppet::Util::Windows::User.admin? }.should raise_error(Puppet::Util::Windows::Error, /Access denied./) end end describe "module function" do let(:username) { 'fabio' } let(:bad_password) { 'goldilocks' } let(:logon_fail_msg) { /Failed to logon user "fabio": Logon failure: unknown user name or bad password./ } def expect_logon_failure_error(&block) expect { yield }.to raise_error { |error| expect(error).to be_a(Puppet::Util::Windows::Error) # http://msdn.microsoft.com/en-us/library/windows/desktop/ms681385(v=vs.85).aspx # ERROR_LOGON_FAILURE 1326 expect(error.code).to eq(1326) } end describe "load_profile" do it "should raise an error when provided with an incorrect username and password" do expect_logon_failure_error { Puppet::Util::Windows::User.load_profile(username, bad_password) } end it "should raise an error when provided with an incorrect username and nil password" do expect_logon_failure_error { Puppet::Util::Windows::User.load_profile(username, nil) } end end describe "logon_user" do it "should raise an error when provided with an incorrect username and password" do expect_logon_failure_error { Puppet::Util::Windows::User.logon_user(username, bad_password) } end it "should raise an error when provided with an incorrect username and nil password" do expect_logon_failure_error { Puppet::Util::Windows::User.logon_user(username, nil) } end end describe "password_is?" do it "should return false given an incorrect username and password" do - Puppet::Util::Windows::User.password_is?(username, bad_password).should be_false + Puppet::Util::Windows::User.password_is?(username, bad_password).should be_falsey end it "should return false given an incorrect username and nil password" do - Puppet::Util::Windows::User.password_is?(username, nil).should be_false + Puppet::Util::Windows::User.password_is?(username, nil).should be_falsey end it "should return false given a nil username and an incorrect password" do - Puppet::Util::Windows::User.password_is?(nil, bad_password).should be_false + Puppet::Util::Windows::User.password_is?(nil, bad_password).should be_falsey end end describe "check_token_membership" do it "should not raise an error" do # added just to call an FFI code path on all platforms lambda { Puppet::Util::Windows::User.check_token_membership }.should_not raise_error 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 017a5ed78..a19ff75bb 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 }.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 }.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 }.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 }.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 }. 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 + option.default.should be_truthy + invoked.should be_truthy 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/agent/locker_spec.rb b/spec/unit/agent/locker_spec.rb index 5a4df3de2..5278c0581 100755 --- a/spec/unit/agent/locker_spec.rb +++ b/spec/unit/agent/locker_spec.rb @@ -1,100 +1,100 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/agent' require 'puppet/agent/locker' class LockerTester include Puppet::Agent::Locker end describe Puppet::Agent::Locker do before do @locker = LockerTester.new end ## These tests are currently very implementation-specific, and they rely heavily on ## having access to the lockfile object. However, I've made this method private ## because it really shouldn't be exposed outside of our implementation... therefore ## these tests have to use a lot of ".send" calls. They should probably be cleaned up ## but for the moment I wanted to make sure not to lose any of the functionality of ## the tests. --cprice 2012-04-16 it "should use a Pidlock instance as its lockfile" do @locker.send(:lockfile).should be_instance_of(Puppet::Util::Pidlock) end it "should use puppet's agent_catalog_run_lockfile' setting to determine its lockfile path" do lockfile = File.expand_path("/my/lock") Puppet[:agent_catalog_run_lockfile] = lockfile lock = Puppet::Util::Pidlock.new(lockfile) Puppet::Util::Pidlock.expects(:new).with(lockfile).returns lock @locker.send(:lockfile) end it "#lockfile_path provides the path to the lockfile" do lockfile = File.expand_path("/my/lock") Puppet[:agent_catalog_run_lockfile] = lockfile @locker.lockfile_path.should == File.expand_path("/my/lock") end it "should reuse the same lock file each time" do @locker.send(:lockfile).should equal(@locker.send(:lockfile)) end it "should have a method that yields when a lock is attained" do @locker.send(:lockfile).expects(:lock).returns true yielded = false @locker.lock do yielded = true end - yielded.should be_true + yielded.should be_truthy end it "should return the block result when the lock method successfully locked" do @locker.send(:lockfile).expects(:lock).returns true @locker.lock { :result }.should == :result end it "should return nil when the lock method does not receive the lock" do @locker.send(:lockfile).expects(:lock).returns false @locker.lock {}.should be_nil end it "should not yield when the lock method does not receive the lock" do @locker.send(:lockfile).expects(:lock).returns false yielded = false @locker.lock { yielded = true } - yielded.should be_false + yielded.should be_falsey end it "should not unlock when a lock was not received" do @locker.send(:lockfile).expects(:lock).returns false @locker.send(:lockfile).expects(:unlock).never @locker.lock {} end it "should unlock after yielding upon obtaining a lock" do @locker.send(:lockfile).stubs(:lock).returns true @locker.send(:lockfile).expects(:unlock) @locker.lock {} end it "should unlock after yielding upon obtaining a lock, even if the block throws an exception" do @locker.send(:lockfile).stubs(:lock).returns true @locker.send(:lockfile).expects(:unlock) lambda { @locker.lock { raise "foo" } }.should raise_error(RuntimeError) end it "should be considered running if the lockfile is locked" do @locker.send(:lockfile).expects(:locked?).returns true @locker.should be_running end end diff --git a/spec/unit/agent_spec.rb b/spec/unit/agent_spec.rb index 95319a624..bc36b7c42 100755 --- a/spec/unit/agent_spec.rb +++ b/spec/unit/agent_spec.rb @@ -1,327 +1,327 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/agent' class AgentTestClient def run # no-op end def stop # no-op end end def without_warnings flag = $VERBOSE $VERBOSE = nil yield $VERBOSE = flag end describe Puppet::Agent do before do Puppet::Status.indirection.stubs(:find).returns Puppet::Status.new("version" => Puppet.version) @agent = Puppet::Agent.new(AgentTestClient, false) # So we don't actually try to hit the filesystem. @agent.stubs(:lock).yields # make Puppet::Application safe for stubbing; restore in an :after block; silence warnings for this. without_warnings { Puppet::Application = Class.new(Puppet::Application) } Puppet::Application.stubs(:clear?).returns(true) Puppet::Application.class_eval do class << self def controlled_run(&block) block.call end end end end after do # restore Puppet::Application from stub-safe subclass, and silence warnings without_warnings { Puppet::Application = Puppet::Application.superclass } end it "should set its client class at initialization" do Puppet::Agent.new("foo", false).client_class.should == "foo" end it "should include the Locker module" do Puppet::Agent.ancestors.should be_include(Puppet::Agent::Locker) end it "should create an instance of its client class and run it when asked to run" do client = mock 'client' AgentTestClient.expects(:new).returns client client.expects(:run) @agent.stubs(:running?).returns false @agent.stubs(:disabled?).returns false @agent.run end it "should be considered running if the lock file is locked" do lockfile = mock 'lockfile' @agent.expects(:lockfile).returns(lockfile) lockfile.expects(:locked?).returns true @agent.should be_running end describe "when being run" do before do AgentTestClient.stubs(:lockfile_path).returns "/my/lock" @agent.stubs(:running?).returns false @agent.stubs(:disabled?).returns false end it "should splay" do @agent.expects(:splay) @agent.run end it "should do nothing if already running" do @agent.expects(:running?).returns true AgentTestClient.expects(:new).never @agent.run end it "should do nothing if disabled" do @agent.expects(:disabled?).returns(true) AgentTestClient.expects(:new).never @agent.run end it "(#11057) should notify the user about why a run is skipped" do Puppet::Application.stubs(:controlled_run).returns(false) Puppet::Application.stubs(:run_status).returns('MOCK_RUN_STATUS') # This is the actual test that we inform the user why the run is skipped. # We assume this information is contained in # Puppet::Application.run_status Puppet.expects(:notice).with(regexp_matches(/MOCK_RUN_STATUS/)) @agent.run end it "should display an informative message if the agent is administratively disabled" do @agent.expects(:disabled?).returns true @agent.expects(:disable_message).returns "foo" Puppet.expects(:notice).with(regexp_matches(/Skipping run of .*; administratively disabled.*\(Reason: 'foo'\)/)) @agent.run end it "should use Puppet::Application.controlled_run to manage process state behavior" do calls = sequence('calls') Puppet::Application.expects(:controlled_run).yields.in_sequence(calls) AgentTestClient.expects(:new).once.in_sequence(calls) @agent.run end it "should not fail if a client class instance cannot be created" do AgentTestClient.expects(:new).raises "eh" Puppet.expects(:err) @agent.run end it "should not fail if there is an exception while running its client" do client = AgentTestClient.new AgentTestClient.expects(:new).returns client client.expects(:run).raises "eh" Puppet.expects(:err) @agent.run end it "should use a filesystem lock to restrict multiple processes running the agent" do client = AgentTestClient.new AgentTestClient.expects(:new).returns client @agent.expects(:lock) client.expects(:run).never # if it doesn't run, then we know our yield is what triggers it @agent.run end it "should make its client instance available while running" do client = AgentTestClient.new AgentTestClient.expects(:new).returns client client.expects(:run).with { @agent.client.should equal(client); true } @agent.run end it "should run the client instance with any arguments passed to it" do client = AgentTestClient.new AgentTestClient.expects(:new).returns client client.expects(:run).with(:pluginsync => true, :other => :options) @agent.run(:other => :options) end it "should return the agent result" do client = AgentTestClient.new AgentTestClient.expects(:new).returns client @agent.expects(:lock).returns(:result) @agent.run.should == :result end describe "when should_fork is true", :if => Puppet.features.posix? do before do @agent = Puppet::Agent.new(AgentTestClient, true) # So we don't actually try to hit the filesystem. @agent.stubs(:lock).yields Kernel.stubs(:fork) Process.stubs(:waitpid2).returns [123, (stub 'process::status', :exitstatus => 0)] @agent.stubs(:exit) end it "should run the agent in a forked process" do client = AgentTestClient.new AgentTestClient.expects(:new).returns client client.expects(:run) Kernel.expects(:fork).yields @agent.run end it "should exit child process if child exit" do client = AgentTestClient.new AgentTestClient.expects(:new).returns client client.expects(:run).raises(SystemExit) Kernel.expects(:fork).yields @agent.expects(:exit).with(-1) @agent.run end it "should re-raise exit happening in the child" do Process.stubs(:waitpid2).returns [123, (stub 'process::status', :exitstatus => -1)] lambda { @agent.run }.should raise_error(SystemExit) end it "should re-raise NoMoreMemory happening in the child" do Process.stubs(:waitpid2).returns [123, (stub 'process::status', :exitstatus => -2)] lambda { @agent.run }.should raise_error(NoMemoryError) end it "should return the child exit code" do Process.stubs(:waitpid2).returns [123, (stub 'process::status', :exitstatus => 777)] @agent.run.should == 777 end it "should return the block exit code as the child exit code" do Kernel.expects(:fork).yields @agent.expects(:exit).with(777) @agent.run_in_fork { 777 } end end describe "on Windows", :if => Puppet.features.microsoft_windows? do it "should never fork" do agent = Puppet::Agent.new(AgentTestClient, true) - expect(agent.should_fork).to be_false + expect(agent.should_fork).to be_falsey end end end describe "when splaying" do before do Puppet[:splay] = true Puppet[:splaylimit] = "10" end it "should do nothing if splay is disabled" do Puppet[:splay] = false @agent.expects(:sleep).never @agent.splay end it "should do nothing if it has already splayed" do @agent.expects(:splayed?).returns true @agent.expects(:sleep).never @agent.splay end it "should log that it is splaying" do @agent.stubs :sleep Puppet.expects :info @agent.splay end it "should sleep for a random portion of the splaylimit plus 1" do Puppet[:splaylimit] = "50" @agent.expects(:rand).with(51).returns 10 @agent.expects(:sleep).with(10) @agent.splay end it "should mark that it has splayed" do @agent.stubs(:sleep) @agent.splay @agent.should be_splayed end end describe "when checking execution state" do describe 'with regular run status' do before :each do Puppet::Application.stubs(:restart_requested?).returns(false) Puppet::Application.stubs(:stop_requested?).returns(false) Puppet::Application.stubs(:interrupted?).returns(false) Puppet::Application.stubs(:clear?).returns(true) end it 'should be false for :stopping?' do - @agent.stopping?.should be_false + @agent.stopping?.should be_falsey end it 'should be false for :needing_restart?' do - @agent.needing_restart?.should be_false + @agent.needing_restart?.should be_falsey end end describe 'with a stop requested' do before :each do Puppet::Application.stubs(:clear?).returns(false) Puppet::Application.stubs(:restart_requested?).returns(false) Puppet::Application.stubs(:stop_requested?).returns(true) Puppet::Application.stubs(:interrupted?).returns(true) end it 'should be true for :stopping?' do - @agent.stopping?.should be_true + @agent.stopping?.should be_truthy end it 'should be false for :needing_restart?' do - @agent.needing_restart?.should be_false + @agent.needing_restart?.should be_falsey end end describe 'with a restart requested' do before :each do Puppet::Application.stubs(:clear?).returns(false) Puppet::Application.stubs(:restart_requested?).returns(true) Puppet::Application.stubs(:stop_requested?).returns(false) Puppet::Application.stubs(:interrupted?).returns(true) end it 'should be false for :stopping?' do - @agent.stopping?.should be_false + @agent.stopping?.should be_falsey end it 'should be true for :needing_restart?' do - @agent.needing_restart?.should be_true + @agent.needing_restart?.should be_truthy end end end end diff --git a/spec/unit/application/agent_spec.rb b/spec/unit/application/agent_spec.rb index a82b54546..81bbbf2f1 100755 --- a/spec/unit/application/agent_spec.rb +++ b/spec/unit/application/agent_spec.rb @@ -1,589 +1,589 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/agent' require 'puppet/application/agent' require 'puppet/network/server' require 'puppet/daemon' describe Puppet::Application::Agent do include PuppetSpec::Files before :each do @puppetd = Puppet::Application[:agent] @daemon = Puppet::Daemon.new(nil) @daemon.stubs(:daemonize) @daemon.stubs(:start) @daemon.stubs(:stop) Puppet::Daemon.stubs(:new).returns(@daemon) Puppet[:daemonize] = false @agent = stub_everything 'agent' Puppet::Agent.stubs(:new).returns(@agent) @puppetd.preinit Puppet::Util::Log.stubs(:newdestination) @ssl_host = stub_everything 'ssl host' Puppet::SSL::Host.stubs(:new).returns(@ssl_host) Puppet::Node.indirection.stubs(:terminus_class=) Puppet::Node.indirection.stubs(:cache_class=) Puppet::Node::Facts.indirection.stubs(:terminus_class=) $stderr.expects(:puts).never Puppet.settings.stubs(:use) end it "should operate in agent run_mode" do @puppetd.class.run_mode.name.should == :agent end it "should declare a main command" do @puppetd.should respond_to(:main) end it "should declare a onetime command" do @puppetd.should respond_to(:onetime) end it "should declare a fingerprint command" do @puppetd.should respond_to(:fingerprint) end it "should declare a preinit block" do @puppetd.should respond_to(:preinit) end describe "in preinit" do it "should catch INT" do Signal.expects(:trap).with { |arg,block| arg == :INT } @puppetd.preinit end it "should init fqdn to nil" do @puppetd.preinit @puppetd.options[:fqdn].should be_nil end it "should init serve to []" do @puppetd.preinit @puppetd.options[:serve].should == [] end it "should use SHA256 as default digest algorithm" do @puppetd.preinit @puppetd.options[:digest].should == 'SHA256' end it "should not fingerprint by default" do @puppetd.preinit - @puppetd.options[:fingerprint].should be_false + @puppetd.options[:fingerprint].should be_falsey end it "should init waitforcert to nil" do @puppetd.preinit @puppetd.options[:waitforcert].should be_nil end end describe "when handling options" do before do @puppetd.command_line.stubs(:args).returns([]) end [:enable, :debug, :fqdn, :test, :verbose, :digest].each do |option| it "should declare handle_#{option} method" do @puppetd.should respond_to("handle_#{option}".to_sym) end it "should store argument value when calling handle_#{option}" do @puppetd.send("handle_#{option}".to_sym, 'arg') @puppetd.options[option].should == 'arg' end end describe "when handling --disable" do it "should set disable to true" do @puppetd.handle_disable('') @puppetd.options[:disable].should == true end it "should store disable message" do @puppetd.handle_disable('message') @puppetd.options[:disable_message].should == 'message' end end it "should set waitforcert to 0 with --onetime and if --waitforcert wasn't given" do @agent.stubs(:run).returns(2) Puppet[:onetime] = true @ssl_host.expects(:wait_for_cert).with(0) expect { execute_agent }.to exit_with 0 end it "should use supplied waitforcert when --onetime is specified" do @agent.stubs(:run).returns(2) Puppet[:onetime] = true @puppetd.handle_waitforcert(60) @ssl_host.expects(:wait_for_cert).with(60) expect { execute_agent }.to exit_with 0 end it "should use a default value for waitforcert when --onetime and --waitforcert are not specified" do @ssl_host.expects(:wait_for_cert).with(120) execute_agent end it "should use the waitforcert setting when checking for a signed certificate" do Puppet[:waitforcert] = 10 @ssl_host.expects(:wait_for_cert).with(10) execute_agent end it "should set the log destination with --logdest" do Puppet::Log.expects(:newdestination).with("console") @puppetd.handle_logdest("console") end it "should put the setdest options to true" do @puppetd.handle_logdest("console") @puppetd.options[:setdest].should == true end it "should parse the log destination from the command line" do @puppetd.command_line.stubs(:args).returns(%w{--logdest /my/file}) Puppet::Util::Log.expects(:newdestination).with("/my/file") @puppetd.parse_options end it "should store the waitforcert options with --waitforcert" do @puppetd.handle_waitforcert("42") @puppetd.options[:waitforcert].should == 42 end end describe "during setup" do before :each do Puppet.stubs(:info) Puppet[:libdir] = "/dev/null/lib" Puppet::Transaction::Report.indirection.stubs(:terminus_class=) Puppet::Transaction::Report.indirection.stubs(:cache_class=) Puppet::Resource::Catalog.indirection.stubs(:terminus_class=) Puppet::Resource::Catalog.indirection.stubs(:cache_class=) Puppet::Node::Facts.indirection.stubs(:terminus_class=) Puppet.stubs(:settraps) end describe "with --test" do it "should call setup_test" do @puppetd.options[:test] = true @puppetd.expects(:setup_test) @puppetd.setup end it "should set options[:verbose] to true" do @puppetd.setup_test @puppetd.options[:verbose].should == true end it "should set options[:onetime] to true" do Puppet[:onetime] = false @puppetd.setup_test Puppet[:onetime].should == true end it "should set options[:detailed_exitcodes] to true" do @puppetd.setup_test @puppetd.options[:detailed_exitcodes].should == true end end it "should call setup_logs" do @puppetd.expects(:setup_logs) @puppetd.setup end describe "when setting up logs" do before :each do Puppet::Util::Log.stubs(:newdestination) end it "should set log level to debug if --debug was passed" do @puppetd.options[:debug] = true @puppetd.setup_logs Puppet::Util::Log.level.should == :debug end it "should set log level to info if --verbose was passed" do @puppetd.options[:verbose] = true @puppetd.setup_logs Puppet::Util::Log.level.should == :info end [:verbose, :debug].each do |level| it "should set console as the log destination with level #{level}" do @puppetd.options[level] = true Puppet::Util::Log.expects(:newdestination).at_least_once Puppet::Util::Log.expects(:newdestination).with(:console).once @puppetd.setup_logs end end it "should set a default log destination if no --logdest" do @puppetd.options[:setdest] = false Puppet::Util::Log.expects(:setup_default) @puppetd.setup_logs end end it "should print puppet config if asked to in Puppet config" do Puppet[:configprint] = "pluginsync" Puppet.settings.expects(:print_configs).returns true expect { execute_agent }.to exit_with 0 end it "should exit after printing puppet config if asked to in Puppet config" do path = make_absolute('/my/path') Puppet[:modulepath] = path Puppet[:configprint] = "modulepath" Puppet::Settings.any_instance.expects(:puts).with(path) expect { execute_agent }.to exit_with 0 end it "should use :main, :puppetd, and :ssl" do Puppet.settings.unstub(:use) Puppet.settings.expects(:use).with(:main, :agent, :ssl) @puppetd.setup end it "should install a remote ca location" do Puppet::SSL::Host.expects(:ca_location=).with(:remote) @puppetd.setup end it "should install a none ca location in fingerprint mode" do @puppetd.options[:fingerprint] = true Puppet::SSL::Host.expects(:ca_location=).with(:none) @puppetd.setup end it "should tell the report handler to use REST" do Puppet::Transaction::Report.indirection.expects(:terminus_class=).with(:rest) @puppetd.setup end it "should tell the report handler to cache locally as yaml" do Puppet::Transaction::Report.indirection.expects(:cache_class=).with(:yaml) @puppetd.setup end it "should default catalog_terminus setting to 'rest'" do @puppetd.initialize_app_defaults Puppet[:catalog_terminus].should == :rest end it "should default node_terminus setting to 'rest'" do @puppetd.initialize_app_defaults Puppet[:node_terminus].should == :rest end it "has an application default :catalog_cache_terminus setting of 'json'" do Puppet::Resource::Catalog.indirection.expects(:cache_class=).with(:json) @puppetd.initialize_app_defaults @puppetd.setup end it "should tell the catalog cache class based on the :catalog_cache_terminus setting" do Puppet[:catalog_cache_terminus] = "yaml" Puppet::Resource::Catalog.indirection.expects(:cache_class=).with(:yaml) @puppetd.initialize_app_defaults @puppetd.setup end it "should not set catalog cache class if :catalog_cache_terminus is explicitly nil" do Puppet[:catalog_cache_terminus] = nil Puppet::Resource::Catalog.indirection.expects(:cache_class=).never @puppetd.initialize_app_defaults @puppetd.setup end it "should default facts_terminus setting to 'facter'" do @puppetd.initialize_app_defaults Puppet[:facts_terminus].should == :facter end it "should create an agent" do Puppet::Agent.stubs(:new).with(Puppet::Configurer) @puppetd.setup end [:enable, :disable].each do |action| it "should delegate to enable_disable_client if we #{action} the agent" do @puppetd.options[action] = true @puppetd.expects(:enable_disable_client).with(@agent) @puppetd.setup end end describe "when enabling or disabling agent" do [:enable, :disable].each do |action| it "should call client.#{action}" do @puppetd.options[action] = true @agent.expects(action) expect { execute_agent }.to exit_with 0 end end it "should pass the disable message when disabling" do @puppetd.options[:disable] = true @puppetd.options[:disable_message] = "message" @agent.expects(:disable).with("message") expect { execute_agent }.to exit_with 0 end it "should pass the default disable message when disabling without a message" do @puppetd.options[:disable] = true @puppetd.options[:disable_message] = nil @agent.expects(:disable).with("reason not specified") expect { execute_agent }.to exit_with 0 end end it "should inform the daemon about our agent if :client is set to 'true'" do @puppetd.options[:client] = true execute_agent @daemon.agent.should == @agent end it "should daemonize if needed" do Puppet.features.stubs(:microsoft_windows?).returns false Puppet[:daemonize] = true @daemon.expects(:daemonize) execute_agent end it "should wait for a certificate" do @puppetd.options[:waitforcert] = 123 @ssl_host.expects(:wait_for_cert).with(123) execute_agent end it "should not wait for a certificate in fingerprint mode" do @puppetd.options[:fingerprint] = true @puppetd.options[:waitforcert] = 123 @puppetd.options[:digest] = 'MD5' certificate = mock 'certificate' certificate.stubs(:digest).with('MD5').returns('ABCDE') @ssl_host.stubs(:certificate).returns(certificate) @ssl_host.expects(:wait_for_cert).never @puppetd.expects(:puts).with('ABCDE') execute_agent end describe "when setting up for fingerprint" do before(:each) do @puppetd.options[:fingerprint] = true end it "should not setup as an agent" do @puppetd.expects(:setup_agent).never @puppetd.setup end it "should not create an agent" do Puppet::Agent.stubs(:new).with(Puppet::Configurer).never @puppetd.setup end it "should not daemonize" do @daemon.expects(:daemonize).never @puppetd.setup end end describe "when configuring agent for catalog run" do it "should set should_fork as true when running normally" do Puppet::Agent.expects(:new).with(anything, true) @puppetd.setup end it "should not set should_fork as false for --onetime" do Puppet[:onetime] = true Puppet::Agent.expects(:new).with(anything, false) @puppetd.setup end end end describe "when running" do before :each do @puppetd.options[:fingerprint] = false end it "should dispatch to fingerprint if --fingerprint is used" do @puppetd.options[:fingerprint] = true @puppetd.stubs(:fingerprint) execute_agent end it "should dispatch to onetime if --onetime is used" do @puppetd.options[:onetime] = true @puppetd.stubs(:onetime) execute_agent end it "should dispatch to main if --onetime and --fingerprint are not used" do @puppetd.options[:onetime] = false @puppetd.stubs(:main) execute_agent end describe "with --onetime" do before :each do @agent.stubs(:run).returns(:report) Puppet[:onetime] = true @puppetd.options[:client] = :client @puppetd.options[:detailed_exitcodes] = false end it "should setup traps" do @daemon.expects(:set_signal_traps) expect { execute_agent }.to exit_with 0 end it "should let the agent run" do @agent.expects(:run).returns(:report) expect { execute_agent }.to exit_with 0 end it "should stop the daemon" do @daemon.expects(:stop).with(:exit => false) expect { execute_agent }.to exit_with 0 end describe "and --detailed-exitcodes" do before :each do @puppetd.options[:detailed_exitcodes] = true end it "should exit with agent computed exit status" do Puppet[:noop] = false @agent.stubs(:run).returns(666) expect { execute_agent }.to exit_with 666 end it "should exit with the agent's exit status, even if --noop is set." do Puppet[:noop] = true @agent.stubs(:run).returns(666) expect { execute_agent }.to exit_with 666 end end end describe "with --fingerprint" do before :each do @cert = mock 'cert' @puppetd.options[:fingerprint] = true @puppetd.options[:digest] = :MD5 end it "should fingerprint the certificate if it exists" do @ssl_host.stubs(:certificate).returns(@cert) @cert.stubs(:digest).with('MD5').returns "fingerprint" @puppetd.expects(:puts).with "fingerprint" @puppetd.fingerprint end it "should fingerprint the certificate request if no certificate have been signed" do @ssl_host.stubs(:certificate).returns(nil) @ssl_host.stubs(:certificate_request).returns(@cert) @cert.stubs(:digest).with('MD5').returns "fingerprint" @puppetd.expects(:puts).with "fingerprint" @puppetd.fingerprint end end describe "without --onetime and --fingerprint" do before :each do Puppet.stubs(:notice) end it "should start our daemon" do @daemon.expects(:start) execute_agent end end end def execute_agent @puppetd.setup @puppetd.run_command end end diff --git a/spec/unit/application/cert_spec.rb b/spec/unit/application/cert_spec.rb index f24ef8677..04de7d0ea 100755 --- a/spec/unit/application/cert_spec.rb +++ b/spec/unit/application/cert_spec.rb @@ -1,220 +1,220 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/application/cert' describe Puppet::Application::Cert => true do before :each do @cert_app = Puppet::Application[:cert] Puppet::Util::Log.stubs(:newdestination) end it "should operate in master run_mode" do @cert_app.class.run_mode.name.should equal(:master) end it "should declare a main command" do @cert_app.should respond_to(:main) end Puppet::SSL::CertificateAuthority::Interface::INTERFACE_METHODS.reject{ |m| m == :destroy }.each do |method| it "should declare option --#{method}" do @cert_app.should respond_to("handle_#{method}".to_sym) end end it "should set log level to info with the --verbose option" do @cert_app.handle_verbose(0) Puppet::Log.level.should == :info end it "should set log level to debug with the --debug option" do @cert_app.handle_debug(0) Puppet::Log.level.should == :debug end it "should set the fingerprint digest with the --digest option" do @cert_app.handle_digest(:digest) @cert_app.digest.should == :digest end it "should set cert_mode to :destroy for --clean" do @cert_app.handle_clean(0) @cert_app.subcommand.should == :destroy end it "should set all to true for --all" do @cert_app.handle_all(0) - @cert_app.all.should be_true + @cert_app.all.should be_truthy end it "should set signed to true for --signed" do @cert_app.handle_signed(0) - @cert_app.signed.should be_true + @cert_app.signed.should be_truthy end Puppet::SSL::CertificateAuthority::Interface::INTERFACE_METHODS.reject { |m| m == :destroy }.each do |method| it "should set cert_mode to #{method} with option --#{method}" do @cert_app.send("handle_#{method}".to_sym, nil) @cert_app.subcommand.should == method end end describe "during setup" do before :each do Puppet::Log.stubs(:newdestination) Puppet::SSL::Host.stubs(:ca_location=) Puppet::SSL::CertificateAuthority.stubs(:new) end it "should set console as the log destination" do Puppet::Log.expects(:newdestination).with(:console) @cert_app.setup end it "should print puppet config if asked to in Puppet config" do Puppet.settings.stubs(:print_configs?).returns(true) Puppet.settings.expects(:print_configs).returns true expect { @cert_app.setup }.to exit_with 0 end it "should exit after printing puppet config if asked to in Puppet config" do Puppet.settings.stubs(:print_configs?).returns(true) expect { @cert_app.setup }.to exit_with 1 end it "should set the CA location to 'only'" do Puppet::SSL::Host.expects(:ca_location=).with(:only) @cert_app.setup end it "should create a new certificate authority" do Puppet::SSL::CertificateAuthority.expects(:new) @cert_app.setup end it "should set the ca_location to :local if the cert_mode is generate" do @cert_app.subcommand = 'generate' Puppet::SSL::Host.expects(:ca_location=).with(:local) @cert_app.setup end it "should set the ca_location to :local if the cert_mode is destroy" do @cert_app.subcommand = 'destroy' Puppet::SSL::Host.expects(:ca_location=).with(:local) @cert_app.setup end it "should set the ca_location to :only if the cert_mode is print" do @cert_app.subcommand = 'print' Puppet::SSL::Host.expects(:ca_location=).with(:only) @cert_app.setup end end describe "when running" do before :each do @cert_app.all = false @ca = stub_everything 'ca' @cert_app.ca = @ca @cert_app.command_line.stubs(:args).returns([]) @iface = stub_everything 'iface' Puppet::SSL::CertificateAuthority::Interface.stubs(:new).returns(@iface) end it "should delegate to the CertificateAuthority" do @iface.expects(:apply) @cert_app.main end it "should delegate with :all if option --all was given" do @cert_app.handle_all(0) Puppet::SSL::CertificateAuthority::Interface.expects(:new).returns(@iface).with { |cert_mode,to| to[:to] == :all } @cert_app.main end it "should delegate to ca.apply with the hosts given on command line" do @cert_app.command_line.stubs(:args).returns(["host"]) Puppet::SSL::CertificateAuthority::Interface.expects(:new).returns(@iface).with { |cert_mode,to| to[:to] == ["host"]} @cert_app.main end it "should send the currently set digest" do @cert_app.command_line.stubs(:args).returns(["host"]) @cert_app.handle_digest(:digest) Puppet::SSL::CertificateAuthority::Interface.expects(:new).returns(@iface).with { |cert_mode,to| to[:digest] == :digest} @cert_app.main end it "should revoke cert if cert_mode is clean" do @cert_app.subcommand = :destroy @cert_app.command_line.stubs(:args).returns(["host"]) Puppet::SSL::CertificateAuthority::Interface.expects(:new).returns(@iface).with { |cert_mode,to| cert_mode == :revoke } Puppet::SSL::CertificateAuthority::Interface.expects(:new).returns(@iface).with { |cert_mode,to| cert_mode == :destroy } @cert_app.main end end describe "when identifying subcommands" do before :each do @cert_app.all = false @ca = stub_everything 'ca' @cert_app.ca = @ca end %w{list revoke generate sign print verify fingerprint}.each do |cmd| short = cmd[0,1] [cmd, "--#{cmd}", "-#{short}"].each do |option| # In our command line '-v' was eaten by 'verbose', so we can't consume # it here; this is a special case from our otherwise standard # processing. --daniel 2011-02-22 next if option == "-v" it "should recognise '#{option}'" do args = [option, "fun.example.com"] @cert_app.command_line.stubs(:args).returns(args) @cert_app.parse_options @cert_app.subcommand.should == cmd.to_sym args.should == ["fun.example.com"] end end end %w{clean --clean -c}.each do |ugly| it "should recognise the '#{ugly}' option as destroy" do args = [ugly, "fun.example.com"] @cert_app.command_line.stubs(:args).returns(args) @cert_app.parse_options @cert_app.subcommand.should == :destroy args.should == ["fun.example.com"] end end it "should print help and exit if there is no subcommand" do args = [] @cert_app.command_line.stubs(:args).returns(args) @cert_app.stubs(:help).returns("I called for help!") @cert_app.expects(:puts).with("I called for help!") expect { @cert_app.parse_options }.to exit_with 0 @cert_app.subcommand.should be_nil end end end diff --git a/spec/unit/application/describe_spec.rb b/spec/unit/application/describe_spec.rb index 2d5f13c89..c417a5a61 100755 --- a/spec/unit/application/describe_spec.rb +++ b/spec/unit/application/describe_spec.rb @@ -1,79 +1,79 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/application/describe' describe Puppet::Application::Describe do before :each do @describe = Puppet::Application[:describe] end it "should declare a main command" do @describe.should respond_to(:main) end it "should declare a preinit block" do @describe.should respond_to(:preinit) end [:providers,:list,:meta].each do |option| it "should declare handle_#{option} method" do @describe.should respond_to("handle_#{option}".to_sym) end it "should store argument value when calling handle_#{option}" do @describe.options.expects(:[]=).with("#{option}".to_sym, 'arg') @describe.send("handle_#{option}".to_sym, 'arg') end end describe "in preinit" do it "should set options[:parameters] to true" do @describe.preinit - @describe.options[:parameters].should be_true + @describe.options[:parameters].should be_truthy end end describe "when handling parameters" do it "should set options[:parameters] to false" do @describe.handle_short(nil) - @describe.options[:parameters].should be_false + @describe.options[:parameters].should be_falsey end end describe "during setup" do it "should collect arguments in options[:types]" do @describe.command_line.stubs(:args).returns(['1','2']) @describe.setup @describe.options[:types].should == ['1','2'] end end describe "when running" do before :each do @typedoc = stub 'type_doc' TypeDoc.stubs(:new).returns(@typedoc) end it "should call list_types if options list is set" do @describe.options[:list] = true @typedoc.expects(:list_types) @describe.run_command end it "should call format_type for each given types" do @describe.options[:list] = false @describe.options[:types] = ['type'] @typedoc.expects(:format_type).with('type', @describe.options) @describe.run_command end end end diff --git a/spec/unit/application/face_base_spec.rb b/spec/unit/application/face_base_spec.rb index ffb213804..a5d3ae7a4 100755 --- a/spec/unit/application/face_base_spec.rb +++ b/spec/unit/application/face_base_spec.rb @@ -1,423 +1,423 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/application/face_base' require 'tmpdir' class Puppet::Application::FaceBase::Basetest < Puppet::Application::FaceBase end describe Puppet::Application::FaceBase do let :app do app = Puppet::Application::FaceBase::Basetest.new app.command_line.stubs(:subcommand_name).returns('subcommand') Puppet::Util::Log.stubs(:newdestination) app end after :each do app.class.clear_everything_for_tests 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 "#parse_options" do before :each do app.command_line.stubs(:args).returns %w{} end describe "with just an action" do before(:each) do # We have to stub Signal.trap to avoid a crazy mess where we take # over signal handling and make it impossible to cancel the test # suite run. # # It would be nice to fix this elsewhere, but it is actually hard to # capture this in rspec 2.5 and all. :( --daniel 2011-04-08 Signal.stubs(:trap) app.command_line.stubs(:args).returns %w{foo} app.preinit app.parse_options end it "should set the face based on the type" do app.face.name.should == :basetest end it "should find the action" do app.action.should be app.action.name.should == :foo end end it "should stop if the first thing found is not an action" do app.command_line.stubs(:args).returns %w{banana count_args} expect { app.run }.to exit_with(1) @logs.map(&:message).should == ["'basetest' has no 'banana' action. See `puppet help basetest`."] end it "should use the default action if not given any arguments" do app.command_line.stubs(:args).returns [] action = stub(:options => [], :render_as => nil) Puppet::Face[:basetest, '0.0.1'].expects(:get_default_action).returns(action) app.stubs(:main) app.run app.action.should == action app.arguments.should == [ { } ] end it "should use the default action if not given a valid one" do app.command_line.stubs(:args).returns %w{bar} action = stub(:options => [], :render_as => nil) Puppet::Face[:basetest, '0.0.1'].expects(:get_default_action).returns(action) app.stubs(:main) app.run app.action.should == action app.arguments.should == [ 'bar', { } ] end it "should have no action if not given a valid one and there is no default action" do app.command_line.stubs(:args).returns %w{bar} Puppet::Face[:basetest, '0.0.1'].expects(:get_default_action).returns(nil) app.stubs(:main) expect { app.run }.to exit_with(1) @logs.first.message.should =~ /has no 'bar' action./ end [%w{something_I_cannot_do}, %w{something_I_cannot_do argument}].each do |input| it "should report unknown actions nicely" do app.command_line.stubs(:args).returns input Puppet::Face[:basetest, '0.0.1'].expects(:get_default_action).returns(nil) app.stubs(:main) expect { app.run }.to exit_with(1) @logs.first.message.should =~ /has no 'something_I_cannot_do' action/ end end [%w{something_I_cannot_do --unknown-option}, %w{something_I_cannot_do argument --unknown-option}].each do |input| it "should report unknown actions even if there are unknown options" do app.command_line.stubs(:args).returns input Puppet::Face[:basetest, '0.0.1'].expects(:get_default_action).returns(nil) app.stubs(:main) expect { app.run }.to exit_with(1) @logs.first.message.should =~ /has no 'something_I_cannot_do' action/ end 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; app.parse_options }. to raise_error(OptionParser::InvalidOption, /invalid 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; app.parse_options }. to raise_error(OptionParser::InvalidOption, /invalid 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; app.parse_options }. to raise_error(OptionParser::InvalidOption, /invalid option: --bar/) end it "should fail if an unknown option is after the action" do app.command_line.stubs(:args).returns %w{foo --bar} expect { app.preinit; app.parse_options }. to raise_error(OptionParser::InvalidOption, /invalid 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 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 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; app.parse_options }. to raise_error(OptionParser::InvalidOption, /invalid option: --bar/) end it "does not skip when a puppet global setting is given as one item" do app.command_line.stubs(:args).returns %w{--confdir=/tmp/puppet foo} app.preinit app.parse_options app.action.name.should == :foo app.options.should == {} end it "does not skip when a puppet global setting is given as two items" do app.command_line.stubs(:args).returns %w{--confdir /tmp/puppet foo} app.preinit app.parse_options app.action.name.should == :foo app.options.should == {} end it "should not add :debug to the application-level options" do app.command_line.stubs(:args).returns %w{--confdir /tmp/puppet foo --debug} app.preinit app.parse_options app.action.name.should == :foo app.options.should == {} end it "should not add :verbose to the application-level options" do app.command_line.stubs(:args).returns %w{--confdir /tmp/puppet foo --verbose} app.preinit app.parse_options app.action.name.should == :foo app.options.should == {} 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 Puppet.settings.initialize_global_settings(args) app.preinit app.parse_options - Puppet[:trace].should be_true + Puppet[:trace].should be_truthy 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 Puppet.settings.initialize_global_settings(args) app.preinit app.parse_options Puppet[:syslogfacility].should == "user1" end end it "should handle application-level options" do app.command_line.stubs(:args).returns %w{--verbose return_true} app.preinit app.parse_options app.face.name.should == :basetest 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 app.parse_options app.setup app.arguments.should == [{ :mandatory => "--bar" }] end it "should pass positional arguments" do myargs = %w{--mandatory --bar foo bar baz quux} app.command_line.stubs(:args).returns(myargs) app.preinit app.parse_options app.setup app.arguments.should == ['bar', 'baz', 'quux', { :mandatory => "--bar" }] end end describe "#main" do before :each do app.stubs(:puts) # don't dump text to screen. app.face = Puppet::Face[:basetest, '0.0.1'] app.action = app.face.get_action(:foo) app.arguments = ["myname", "myarg"] end it "should send the specified verb and name to the face" do app.face.expects(:foo).with(*app.arguments) expect { app.main }.to exit_with(0) end it "should lookup help when it cannot do anything else" do app.action = nil Puppet::Face[:help, :current].expects(:help).with(:basetest) expect { app.main }.to exit_with(1) end it "should use its render method to render any result" do app.expects(:render).with(app.arguments.length + 1, ["myname", "myarg"]) expect { app.main }.to exit_with(0) end end describe "error reporting" do before :each do app.stubs(:puts) # don't dump text to screen. app.render_as = :json app.face = Puppet::Face[:basetest, '0.0.1'] app.arguments = [{}] # we always have options in there... end it "should exit 0 when the action returns true" do app.action = app.face.get_action :return_true expect { app.main }.to exit_with(0) end it "should exit 0 when the action returns false" do app.action = app.face.get_action :return_false expect { app.main }.to exit_with(0) end it "should exit 0 when the action returns nil" do app.action = app.face.get_action :return_nil expect { app.main }.to exit_with(0) end it "should exit non-0 when the action raises" do app.action = app.face.get_action :return_raise expect { app.main }.not_to exit_with(0) end it "should use the exit code set by the action" do app.action = app.face.get_action :with_specific_exit_code expect { app.main }.to exit_with(5) end end describe "#render" do before :each do app.face = Puppet::Interface.new('basetest', '0.0.1') app.action = Puppet::Interface::Action.new(app.face, :foo) end context "default rendering" do before :each do app.setup end ["hello", 1, 1.0].each do |input| it "should just return a #{input.class.name}" do app.render(input, {}).should == input end end [[1, 2], ["one"], [{ 1 => 1 }]].each do |input| it "should render Array as one item per line" do app.render(input, {}).should == input.collect { |item| item.to_s + "\n" }.join('') end end it "should render a non-trivially-keyed Hash with using JSON" do hash = { [1,2] => 3, [2,3] => 5, [3,4] => 7 } app.render(hash, {}).should == hash.to_pson.chomp end it "should render a {String,Numeric}-keyed Hash into a table" do object = Object.new hash = { "one" => 1, "two" => [], "three" => {}, "four" => object, 5 => 5, 6.0 => 6 } # Gotta love ASCII-betical sort order. Hope your objects are better # structured for display than my test one is. --daniel 2011-04-18 app.render(hash, {}).should == < { "1" => '1' * 40, "2" => '2' * 40, '3' => '3' * 40 }, "text" => { "a" => 'a' * 40, 'b' => 'b' * 40, 'c' => 'c' * 40 } } app.render(hash, {}).should == <"1111111111111111111111111111111111111111", "2"=>"2222222222222222222222222222222222222222", "3"=>"3333333333333333333333333333333333333333"} text {"a"=>"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "b"=>"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", "c"=>"cccccccccccccccccccccccccccccccccccccccc"} EOT end describe "when setting the rendering method" do after do # need to reset the when_rendering block so that other tests can set it later app.action.instance_variable_set("@when_rendering", {}) end it "should invoke the action rendering hook while rendering" do app.action.set_rendering_method_for(:console, proc { |value| "bi-winning!" }) app.render("bi-polar?", {}).should == "bi-winning!" end it "should invoke the action rendering hook with args and options while rendering" do app.action.instance_variable_set("@when_rendering", {}) app.action.when_invoked = proc { |name, options| 'just need to match arity for rendering' } app.action.set_rendering_method_for( :console, proc { |value, name, options| "I'm #{name}, no wait, I'm #{options[:altername]}" } ) app.render("bi-polar?", ['bob', {:altername => 'sue'}]).should == "I'm bob, no wait, I'm sue" end end it "should render JSON when asked for json" do app.render_as = :json json = app.render({ :one => 1, :two => 2 }, {}) json.should =~ /"one":\s*1\b/ json.should =~ /"two":\s*2\b/ PSON.parse(json).should == { "one" => 1, "two" => 2 } end end it "should fail early if asked to render an invalid format" do app.command_line.stubs(:args).returns %w{--render-as interpretive-dance return_true} # We shouldn't get here, thanks to the exception, and our expectation on # it, but this helps us fail if that slips up and all. --daniel 2011-04-27 Puppet::Face[:help, :current].expects(:help).never Puppet.expects(:err).with("Could not parse application options: I don't know how to render 'interpretive-dance'") expect { app.run }.to exit_with(1) end it "should work if asked to render a NetworkHandler format" do app.command_line.stubs(:args).returns %w{count_args a b c --render-as pson} expect { expect { app.run }.to exit_with(0) }.to have_printed(/3/) end it "should invoke when_rendering hook 's' when asked to render-as 's'" do app.command_line.stubs(:args).returns %w{with_s_rendering_hook --render-as s} app.action = app.face.get_action(:with_s_rendering_hook) expect { expect { app.run }.to exit_with(0) }.to have_printed(/you invoked the 's' rendering hook/) end end end diff --git a/spec/unit/application_spec.rb b/spec/unit/application_spec.rb index 11f7440a6..9879433ea 100755 --- a/spec/unit/application_spec.rb +++ b/spec/unit/application_spec.rb @@ -1,641 +1,641 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/application' require 'puppet' require 'getoptlong' require 'timeout' describe Puppet::Application do before(:each) do @app = Class.new(Puppet::Application).new @appclass = @app.class @app.stubs(:name).returns("test_app") end describe "application commandline" do it "should not pick up changes to the array of arguments" do args = %w{subcommand --arg} command_line = Puppet::Util::CommandLine.new('puppet', args) app = Puppet::Application.new(command_line) args[0] = 'different_subcommand' args[1] = '--other-arg' app.command_line.subcommand_name.should == 'subcommand' app.command_line.args.should == ['--arg'] end end describe "application defaults" do it "should fail if required app default values are missing" do @app.stubs(:app_defaults).returns({ :foo => 'bar' }) Puppet.expects(:err).with(regexp_matches(/missing required app default setting/)) expect { @app.run }.to exit_with(1) end 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" do expect { @klass.find("String") }.to raise_error(LoadError) end it "should error if it can't find a class" do Puppet.expects(:err).with do |value| value =~ /Unable to find application 'ThisShallNeverEverEverExist'/ and value =~ /puppet\/application\/thisshallneverevereverexist/ and value =~ /no such file to load|cannot load such file/ end expect { @klass.find("ThisShallNeverEverEverExist") }.to raise_error(LoadError) 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 "#available_application_names" do it 'should be able to find available application names' do apps = %w{describe filebucket kick queue resource agent cert apply doc master} Puppet::Util::Autoload.expects(:files_to_load).returns(apps) Puppet::Application.available_application_names.should =~ apps end it 'should find applications from multiple paths' do Puppet::Util::Autoload.expects(:files_to_load).with('puppet/application').returns(%w{ /a/foo.rb /b/bar.rb }) Puppet::Application.available_application_names.should =~ %w{ foo bar } end it 'should return unique application names' do Puppet::Util::Autoload.expects(:files_to_load).with('puppet/application').returns(%w{ /a/foo.rb /b/foo.rb }) Puppet::Application.available_application_names.should == %w{ foo } 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 # These tests may look a little weird and repetative in its current state; # it used to illustrate several ways that the run_mode could be changed # at run time; there are fewer ways now, but it would still be nice to # get to a point where it was entirely impossible. describe "when dealing with run_mode" do class TestApp < Puppet::Application run_mode :master def run_command # no-op end end it "should sadly and frighteningly allow run_mode to change at runtime via #initialize_app_defaults" do Puppet.features.stubs(:syslog?).returns(true) app = TestApp.new app.initialize_app_defaults Puppet.run_mode.should be_master end it "should sadly and frighteningly allow run_mode to change at runtime via #run" do app = TestApp.new app.run app.class.run_mode.name.should == :master Puppet.run_mode.should be_master end end it "should explode when an invalid run mode is set at runtime, for great victory" do expect { class InvalidRunModeTestApp < Puppet::Application run_mode :abracadabra def run_command # no-op end end }.to raise_error end 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 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 + Puppet::Application.restart_requested?.should be_falsey end it 'should return false for stop_requested?' do - Puppet::Application.stop_requested?.should be_false + Puppet::Application.stop_requested?.should be_falsey end it 'should return false for interrupted?' do - Puppet::Application.interrupted?.should be_false + Puppet::Application.interrupted?.should be_falsey end it 'should return true for clear?' do - Puppet::Application.clear?.should be_true + Puppet::Application.clear?.should be_truthy 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 + Puppet::Application.stop_requested?.should be_truthy end it 'should return false for restart_requested?' do - Puppet::Application.restart_requested?.should be_false + Puppet::Application.restart_requested?.should be_falsey end it 'should return true for interrupted?' do - Puppet::Application.interrupted?.should be_true + Puppet::Application.interrupted?.should be_truthy end it 'should return false for clear?' do - Puppet::Application.clear?.should be_false + Puppet::Application.clear?.should be_falsey 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 + Puppet::Application.restart_requested?.should be_truthy end it 'should return false for stop_requested?' do - Puppet::Application.stop_requested?.should be_false + Puppet::Application.stop_requested?.should be_falsey end it 'should return true for interrupted?' do - Puppet::Application.interrupted?.should be_true + Puppet::Application.interrupted?.should be_truthy end it 'should return false for clear?' do - Puppet::Application.clear?.should be_false + Puppet::Application.clear?.should be_falsey 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' do Timeout::timeout(3) do # if the signal doesn't fire, this causes failure. has_run = false old_handler = trap('HUP') { has_run = true } begin Puppet::Application.controlled_run do Puppet::Application.run_status = :restart_requested end # Ruby 1.9 uses a separate OS level thread to run the signal # handler, so we have to poll - ideally, in a way that will kick # the OS into running other threads - for a while. # # You can't just use the Ruby Thread yield thing either, because # that is just an OS hint, and Linux ... doesn't take that # seriously. --daniel 2012-03-22 sleep 0.001 while not has_run ensure trap('HUP', old_handler) end 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 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 transform boolean option to normal form for Puppet.settings" do @app.expects(:handle_unknown).with("--option", true) @app.send(:handlearg, "--[no-]option", true) end it "should transform boolean option to no- form for Puppet.settings" do @app.expects(:handle_unknown).with("--no-option", false) @app.send(:handlearg, "--[no-]option", false) end end end describe "when calling default setup" do before :each do @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 it "does not downgrade the loglevel when --verbose is specified" do Puppet[:log_level] = :debug @app.options.stubs(:[]).with(:verbose).returns(true) @app.setup_logs Puppet::Util::Log.level.should == :debug end it "allows the loglevel to be specified as an argument" do @app.set_log_level(:debug => true) Puppet::Util::Log.level.should == :debug 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 }.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 }.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 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 Puppet.expects(:err) 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) Puppet.expects(:err) 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) Puppet.expects(:err) 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 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 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 describe "#handle_logdest_arg" do let(:test_arg) { "arg_test_logdest" } it "should log an exception that is raised" do our_exception = Puppet::DevError.new("test exception") Puppet::Util::Log.expects(:newdestination).with(test_arg).raises(our_exception) Puppet.expects(:log_exception).with(our_exception) @app.handle_logdest_arg(test_arg) end it "should set the new log destination" do Puppet::Util::Log.expects(:newdestination).with(test_arg) @app.handle_logdest_arg(test_arg) end it "should set the flag that a destination is set in the options hash" do Puppet::Util::Log.stubs(:newdestination).with(test_arg) @app.handle_logdest_arg(test_arg) - @app.options[:setdest].should be_true + @app.options[:setdest].should be_truthy end end end diff --git a/spec/unit/configurer/downloader_spec.rb b/spec/unit/configurer/downloader_spec.rb index 4aa74afaf..5ea7d5d8c 100755 --- a/spec/unit/configurer/downloader_spec.rb +++ b/spec/unit/configurer/downloader_spec.rb @@ -1,222 +1,222 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/configurer/downloader' describe Puppet::Configurer::Downloader do require 'puppet_spec/files' include PuppetSpec::Files let(:path) { Puppet[:plugindest] } let(:source) { 'puppet://puppet/plugins' } it "should require a name" do lambda { Puppet::Configurer::Downloader.new }.should raise_error(ArgumentError) end it "should require a path and a source at initialization" do lambda { Puppet::Configurer::Downloader.new("name") }.should raise_error(ArgumentError) end it "should set the name, path and source appropriately" do dler = Puppet::Configurer::Downloader.new("facts", "path", "source") dler.name.should == "facts" dler.path.should == "path" dler.source.should == "source" end def downloader(options = {}) options[:name] ||= "facts" options[:path] ||= path options[:source_permissions] ||= :ignore Puppet::Configurer::Downloader.new(options[:name], options[:path], source, options[:ignore], options[:environment], options[:source_permissions]) end def generate_file_resource(options = {}) dler = downloader(options) dler.file end describe "when creating the file that does the downloading" do it "should create a file instance with the right path and source" do file = generate_file_resource(:path => path, :source => source) expect(file[:path]).to eq(path) expect(file[:source]).to eq([source]) end it "should tag the file with the downloader name" do name = "mydownloader" file = generate_file_resource(:name => name) expect(file[:tag]).to eq([name]) end it "should always recurse" do file = generate_file_resource - expect(file[:recurse]).to be_true + expect(file[:recurse]).to be_truthy end it "should always purge" do file = generate_file_resource - expect(file[:purge]).to be_true + expect(file[:purge]).to be_truthy end it "should never be in noop" do file = generate_file_resource - expect(file[:noop]).to be_false + expect(file[:noop]).to be_falsey end it "should set source_permissions to ignore by default" do file = generate_file_resource expect(file[:source_permissions]).to eq(:ignore) end it "should allow source_permissions to be overridden" do file = generate_file_resource(:source_permissions => :use) expect(file[:source_permissions]).to eq(:use) end describe "on POSIX", :if => Puppet.features.posix? do it "should always set the owner to the current UID" do Process.expects(:uid).returns 51 file = generate_file_resource(:path => '/path') expect(file[:owner]).to eq(51) end it "should always set the group to the current GID" do Process.expects(:gid).returns 61 file = generate_file_resource(:path => '/path') expect(file[:group]).to eq(61) end end describe "on Windows", :if => Puppet.features.microsoft_windows? do it "should omit the owner" do file = generate_file_resource(:path => 'C:/path') expect(file[:owner]).to be_nil end it "should omit the group" do file = generate_file_resource(:path => 'C:/path') expect(file[:group]).to be_nil end end it "should always force the download" do file = generate_file_resource - expect(file[:force]).to be_true + expect(file[:force]).to be_truthy end it "should never back up when downloading" do file = generate_file_resource - expect(file[:backup]).to be_false + expect(file[:backup]).to be_falsey end it "should support providing an 'ignore' parameter" do file = generate_file_resource(:ignore => '.svn') expect(file[:ignore]).to eq(['.svn']) end it "should split the 'ignore' parameter on whitespace" do file = generate_file_resource(:ignore => '.svn CVS') expect(file[:ignore]).to eq(['.svn', 'CVS']) end end describe "when creating the catalog to do the downloading" do before do @path = make_absolute("/download/path") @dler = Puppet::Configurer::Downloader.new("foo", @path, make_absolute("source")) end it "should create a catalog and add the file to it" do catalog = @dler.catalog catalog.resources.size.should == 1 catalog.resources.first.class.should == Puppet::Type::File catalog.resources.first.name.should == @path end it "should specify that it is not managing a host catalog" do @dler.catalog.host_config.should == false end end describe "when downloading" do before do @dl_name = tmpfile("downloadpath") source_name = tmpfile("source") File.open(source_name, 'w') {|f| f.write('hola mundo') } env = Puppet::Node::Environment.remote('foo') @dler = Puppet::Configurer::Downloader.new("foo", @dl_name, source_name, Puppet[:pluginsignore], env) end it "should not skip downloaded resources when filtering on tags" do Puppet[:tags] = 'maytag' @dler.evaluate - Puppet::FileSystem.exist?(@dl_name).should be_true + Puppet::FileSystem.exist?(@dl_name).should be_truthy end it "should log that it is downloading" do Puppet.expects(:info) @dler.evaluate end it "should return all changed file paths" do trans = mock 'transaction' catalog = mock 'catalog' @dler.expects(:catalog).returns(catalog) catalog.expects(:apply).yields(trans) resource = mock 'resource' resource.expects(:[]).with(:path).returns "/changed/file" trans.expects(:changed?).returns([resource]) @dler.evaluate.should == %w{/changed/file} end it "should yield the resources if a block is given" do trans = mock 'transaction' catalog = mock 'catalog' @dler.expects(:catalog).returns(catalog) catalog.expects(:apply).yields(trans) resource = mock 'resource' resource.expects(:[]).with(:path).returns "/changed/file" trans.expects(:changed?).returns([resource]) yielded = nil @dler.evaluate { |r| yielded = r } yielded.should == resource end it "should catch and log exceptions" do Puppet.expects(:err) # The downloader creates a new catalog for each apply, and really the only object # that it is possible to stub for the purpose of generating a puppet error Puppet::Resource::Catalog.any_instance.stubs(:apply).raises(Puppet::Error, "testing") lambda { @dler.evaluate }.should_not raise_error end end end diff --git a/spec/unit/configurer_spec.rb b/spec/unit/configurer_spec.rb index fdcb6acb6..3d5db5abb 100755 --- a/spec/unit/configurer_spec.rb +++ b/spec/unit/configurer_spec.rb @@ -1,680 +1,680 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/configurer' describe Puppet::Configurer do before do Puppet.settings.stubs(:use).returns(true) @agent = Puppet::Configurer.new @agent.stubs(:init_storage) Puppet::Util::Storage.stubs(:store) Puppet[:server] = "puppetmaster" Puppet[:report] = true end it "should include the Fact Handler module" do Puppet::Configurer.ancestors.should be_include(Puppet::Configurer::FactHandler) end describe "when executing a pre-run hook" do it "should do nothing if the hook is set to an empty string" do Puppet.settings[:prerun_command] = "" Puppet::Util.expects(:exec).never @agent.execute_prerun_command end it "should execute any pre-run command provided via the 'prerun_command' setting" do Puppet.settings[:prerun_command] = "/my/command" Puppet::Util::Execution.expects(:execute).with(["/my/command"]).raises(Puppet::ExecutionFailure, "Failed") @agent.execute_prerun_command end it "should fail if the command fails" do Puppet.settings[:prerun_command] = "/my/command" Puppet::Util::Execution.expects(:execute).with(["/my/command"]).raises(Puppet::ExecutionFailure, "Failed") - @agent.execute_prerun_command.should be_false + @agent.execute_prerun_command.should be_falsey end end describe "when executing a post-run hook" do it "should do nothing if the hook is set to an empty string" do Puppet.settings[:postrun_command] = "" Puppet::Util.expects(:exec).never @agent.execute_postrun_command end it "should execute any post-run command provided via the 'postrun_command' setting" do Puppet.settings[:postrun_command] = "/my/command" Puppet::Util::Execution.expects(:execute).with(["/my/command"]).raises(Puppet::ExecutionFailure, "Failed") @agent.execute_postrun_command end it "should fail if the command fails" do Puppet.settings[:postrun_command] = "/my/command" Puppet::Util::Execution.expects(:execute).with(["/my/command"]).raises(Puppet::ExecutionFailure, "Failed") - @agent.execute_postrun_command.should be_false + @agent.execute_postrun_command.should be_falsey end end describe "when executing a catalog run" do before do Puppet.settings.stubs(:use).returns(true) @agent.stubs(:download_plugins) Puppet::Node::Facts.indirection.terminus_class = :memory @facts = Puppet::Node::Facts.new(Puppet[:node_name_value]) Puppet::Node::Facts.indirection.save(@facts) @catalog = Puppet::Resource::Catalog.new("tester", Puppet::Node::Environment.remote(Puppet[:environment].to_sym)) @catalog.stubs(:to_ral).returns(@catalog) Puppet::Resource::Catalog.indirection.terminus_class = :rest Puppet::Resource::Catalog.indirection.stubs(:find).returns(@catalog) @agent.stubs(:send_report) @agent.stubs(:save_last_run_summary) Puppet::Util::Log.stubs(:close_all) end after :all do Puppet::Node::Facts.indirection.reset_terminus_class Puppet::Resource::Catalog.indirection.reset_terminus_class end it "should initialize storage" do Puppet::Util::Storage.expects(:load) @agent.run end it "downloads plugins when told" do @agent.expects(:download_plugins) @agent.run(:pluginsync => true) end it "does not download plugins when told" do @agent.expects(:download_plugins).never @agent.run(:pluginsync => false) end it "should carry on when it can't fetch its node definition" do error = Net::HTTPError.new(400, 'dummy server communication error') Puppet::Node.indirection.expects(:find).raises(error) @agent.run.should == 0 end it "applies a cached catalog when it can't connect to the master" do error = Errno::ECONNREFUSED.new('Connection refused - connect(2)') Puppet::Node.indirection.expects(:find).raises(error) Puppet::Resource::Catalog.indirection.expects(:find).with(anything, has_entry(:ignore_cache => true)).raises(error) Puppet::Resource::Catalog.indirection.expects(:find).with(anything, has_entry(:ignore_terminus => true)).returns(@catalog) @agent.run.should == 0 end it "should initialize a transaction report if one is not provided" do report = Puppet::Transaction::Report.new("apply") Puppet::Transaction::Report.expects(:new).returns report @agent.run end it "should respect node_name_fact when setting the host on a report" do Puppet[:node_name_fact] = 'my_name_fact' @facts.values = {'my_name_fact' => 'node_name_from_fact'} report = Puppet::Transaction::Report.new("apply") @agent.run(:report => report) report.host.should == 'node_name_from_fact' end it "should pass the new report to the catalog" do report = Puppet::Transaction::Report.new("apply") Puppet::Transaction::Report.stubs(:new).returns report @catalog.expects(:apply).with{|options| options[:report] == report} @agent.run end it "should use the provided report if it was passed one" do report = Puppet::Transaction::Report.new("apply") @catalog.expects(:apply).with {|options| options[:report] == report} @agent.run(:report => report) end it "should set the report as a log destination" do report = Puppet::Transaction::Report.new("apply") report.expects(:<<).with(instance_of(Puppet::Util::Log)).at_least_once @agent.run(:report => report) end it "should retrieve the catalog" do @agent.expects(:retrieve_catalog) @agent.run end it "should log a failure and do nothing if no catalog can be retrieved" do @agent.expects(:retrieve_catalog).returns nil Puppet.expects(:err).with "Could not retrieve catalog; skipping run" @agent.run end it "should apply the catalog with all options to :run" do @agent.expects(:retrieve_catalog).returns @catalog @catalog.expects(:apply).with { |args| args[:one] == true } @agent.run :one => true end it "should accept a catalog and use it instead of retrieving a different one" do @agent.expects(:retrieve_catalog).never @catalog.expects(:apply) @agent.run :one => true, :catalog => @catalog end it "should benchmark how long it takes to apply the catalog" do @agent.expects(:benchmark).with(:notice, instance_of(String)) @agent.expects(:retrieve_catalog).returns @catalog @catalog.expects(:apply).never # because we're not yielding @agent.run end it "should execute post-run hooks after the run" do @agent.expects(:execute_postrun_command) @agent.run end it "should send the report" do report = Puppet::Transaction::Report.new("apply", nil, "test", "aaaa") Puppet::Transaction::Report.expects(:new).returns(report) @agent.expects(:send_report).with(report) report.environment.should == "test" report.transaction_uuid.should == "aaaa" @agent.run end it "should send the transaction report even if the catalog could not be retrieved" do @agent.expects(:retrieve_catalog).returns nil report = Puppet::Transaction::Report.new("apply", nil, "test", "aaaa") Puppet::Transaction::Report.expects(:new).returns(report) @agent.expects(:send_report).with(report) report.environment.should == "test" report.transaction_uuid.should == "aaaa" @agent.run end it "should send the transaction report even if there is a failure" do @agent.expects(:retrieve_catalog).raises "whatever" report = Puppet::Transaction::Report.new("apply", nil, "test", "aaaa") Puppet::Transaction::Report.expects(:new).returns(report) @agent.expects(:send_report).with(report) report.environment.should == "test" report.transaction_uuid.should == "aaaa" @agent.run.should be_nil end it "should remove the report as a log destination when the run is finished" do report = Puppet::Transaction::Report.new("apply") Puppet::Transaction::Report.expects(:new).returns(report) @agent.run Puppet::Util::Log.destinations.should_not include(report) end it "should return the report exit_status as the result of the run" do report = Puppet::Transaction::Report.new("apply") Puppet::Transaction::Report.expects(:new).returns(report) report.expects(:exit_status).returns(1234) @agent.run.should == 1234 end it "should send the transaction report even if the pre-run command fails" do report = Puppet::Transaction::Report.new("apply") Puppet::Transaction::Report.expects(:new).returns(report) Puppet.settings[:prerun_command] = "/my/command" Puppet::Util::Execution.expects(:execute).with(["/my/command"]).raises(Puppet::ExecutionFailure, "Failed") @agent.expects(:send_report).with(report) @agent.run.should be_nil end it "should include the pre-run command failure in the report" do report = Puppet::Transaction::Report.new("apply") Puppet::Transaction::Report.expects(:new).returns(report) Puppet.settings[:prerun_command] = "/my/command" Puppet::Util::Execution.expects(:execute).with(["/my/command"]).raises(Puppet::ExecutionFailure, "Failed") @agent.run.should be_nil report.logs.find { |x| x.message =~ /Could not run command from prerun_command/ }.should be end it "should send the transaction report even if the post-run command fails" do report = Puppet::Transaction::Report.new("apply") Puppet::Transaction::Report.expects(:new).returns(report) Puppet.settings[:postrun_command] = "/my/command" Puppet::Util::Execution.expects(:execute).with(["/my/command"]).raises(Puppet::ExecutionFailure, "Failed") @agent.expects(:send_report).with(report) @agent.run.should be_nil end it "should include the post-run command failure in the report" do report = Puppet::Transaction::Report.new("apply") Puppet::Transaction::Report.expects(:new).returns(report) Puppet.settings[:postrun_command] = "/my/command" Puppet::Util::Execution.expects(:execute).with(["/my/command"]).raises(Puppet::ExecutionFailure, "Failed") report.expects(:<<).with { |log| log.message.include?("Could not run command from postrun_command") } @agent.run.should be_nil end it "should execute post-run command even if the pre-run command fails" do Puppet.settings[:prerun_command] = "/my/precommand" Puppet.settings[:postrun_command] = "/my/postcommand" Puppet::Util::Execution.expects(:execute).with(["/my/precommand"]).raises(Puppet::ExecutionFailure, "Failed") Puppet::Util::Execution.expects(:execute).with(["/my/postcommand"]) @agent.run.should be_nil end it "should finalize the report" do report = Puppet::Transaction::Report.new("apply") Puppet::Transaction::Report.expects(:new).returns(report) report.expects(:finalize_report) @agent.run end it "should not apply the catalog if the pre-run command fails" do report = Puppet::Transaction::Report.new("apply") Puppet::Transaction::Report.expects(:new).returns(report) Puppet.settings[:prerun_command] = "/my/command" Puppet::Util::Execution.expects(:execute).with(["/my/command"]).raises(Puppet::ExecutionFailure, "Failed") @catalog.expects(:apply).never() @agent.expects(:send_report) @agent.run.should be_nil end it "should apply the catalog, send the report, and return nil if the post-run command fails" do report = Puppet::Transaction::Report.new("apply") Puppet::Transaction::Report.expects(:new).returns(report) Puppet.settings[:postrun_command] = "/my/command" Puppet::Util::Execution.expects(:execute).with(["/my/command"]).raises(Puppet::ExecutionFailure, "Failed") @catalog.expects(:apply) @agent.expects(:send_report) @agent.run.should be_nil end it "should refetch the catalog if the server specifies a new environment in the catalog" do catalog = Puppet::Resource::Catalog.new("tester", Puppet::Node::Environment.remote('second_env')) @agent.expects(:retrieve_catalog).returns(catalog).twice @agent.run end it "should change the environment setting if the server specifies a new environment in the catalog" do @catalog.stubs(:environment).returns("second_env") @agent.run @agent.environment.should == "second_env" end it "should fix the report if the server specifies a new environment in the catalog" do report = Puppet::Transaction::Report.new("apply", nil, "test", "aaaa") Puppet::Transaction::Report.expects(:new).returns(report) @agent.expects(:send_report).with(report) @catalog.stubs(:environment).returns("second_env") @agent.stubs(:retrieve_catalog).returns(@catalog) @agent.run report.environment.should == "second_env" end it "should clear the global caches" do $env_module_directories = false @agent.run $env_module_directories.should == nil end describe "when not using a REST terminus for catalogs" do it "should not pass any facts when retrieving the catalog" do Puppet::Resource::Catalog.indirection.terminus_class = :compiler @agent.expects(:facts_for_uploading).never Puppet::Resource::Catalog.indirection.expects(:find).with { |name, options| options[:facts].nil? }.returns @catalog @agent.run end end describe "when using a REST terminus for catalogs" do it "should pass the prepared facts and the facts format as arguments when retrieving the catalog" do Puppet::Resource::Catalog.indirection.terminus_class = :rest @agent.expects(:facts_for_uploading).returns(:facts => "myfacts", :facts_format => :foo) Puppet::Resource::Catalog.indirection.expects(:find).with { |name, options| options[:facts] == "myfacts" and options[:facts_format] == :foo }.returns @catalog @agent.run end end end describe "when sending a report" do include PuppetSpec::Files before do Puppet.settings.stubs(:use).returns(true) @configurer = Puppet::Configurer.new Puppet[:lastrunfile] = tmpfile('last_run_file') @report = Puppet::Transaction::Report.new("apply") Puppet[:reports] = "none" end it "should print a report summary if configured to do so" do Puppet.settings[:summarize] = true @report.expects(:summary).returns "stuff" @configurer.expects(:puts).with("stuff") @configurer.send_report(@report) end it "should not print a report summary if not configured to do so" do Puppet.settings[:summarize] = false @configurer.expects(:puts).never @configurer.send_report(@report) end it "should save the report if reporting is enabled" do Puppet.settings[:report] = true Puppet::Transaction::Report.indirection.expects(:save).with(@report, nil, instance_of(Hash)) @configurer.send_report(@report) end it "should not save the report if reporting is disabled" do Puppet.settings[:report] = false Puppet::Transaction::Report.indirection.expects(:save).with(@report, nil, instance_of(Hash)).never @configurer.send_report(@report) end it "should save the last run summary if reporting is enabled" do Puppet.settings[:report] = true @configurer.expects(:save_last_run_summary).with(@report) @configurer.send_report(@report) end it "should save the last run summary if reporting is disabled" do Puppet.settings[:report] = false @configurer.expects(:save_last_run_summary).with(@report) @configurer.send_report(@report) end it "should log but not fail if saving the report fails" do Puppet.settings[:report] = true Puppet::Transaction::Report.indirection.expects(:save).raises("whatever") Puppet.expects(:err) lambda { @configurer.send_report(@report) }.should_not raise_error end end describe "when saving the summary report file" do include PuppetSpec::Files before do Puppet.settings.stubs(:use).returns(true) @configurer = Puppet::Configurer.new @report = stub 'report', :raw_summary => {} Puppet[:lastrunfile] = tmpfile('last_run_file') end it "should write the last run file" do @configurer.save_last_run_summary(@report) - Puppet::FileSystem.exist?(Puppet[:lastrunfile]).should be_true + Puppet::FileSystem.exist?(Puppet[:lastrunfile]).should be_truthy end it "should write the raw summary as yaml" do @report.expects(:raw_summary).returns("summary") @configurer.save_last_run_summary(@report) File.read(Puppet[:lastrunfile]).should == YAML.dump("summary") end it "should log but not fail if saving the last run summary fails" do # The mock will raise an exception on any method used. This should # simulate a nice hard failure from the underlying OS for us. fh = Class.new(Object) do def method_missing(*args) raise "failed to do #{args[0]}" end end.new Puppet::Util.expects(:replace_file).yields(fh) Puppet.expects(:err) expect { @configurer.save_last_run_summary(@report) }.to_not raise_error end it "should create the last run file with the correct mode" do Puppet.settings.setting(:lastrunfile).expects(:mode).returns('664') @configurer.save_last_run_summary(@report) if Puppet::Util::Platform.windows? require 'puppet/util/windows/security' mode = Puppet::Util::Windows::Security.get_mode(Puppet[:lastrunfile]) else mode = Puppet::FileSystem.stat(Puppet[:lastrunfile]).mode end (mode & 0777).should == 0664 end it "should report invalid last run file permissions" do Puppet.settings.setting(:lastrunfile).expects(:mode).returns('892') Puppet.expects(:err).with(regexp_matches(/Could not save last run local report.*892 is invalid/)) @configurer.save_last_run_summary(@report) end end describe "when requesting a node" do it "uses the transaction uuid in the request" do Puppet::Node.indirection.expects(:find).with(anything, has_entries(:transaction_uuid => anything)).twice @agent.run end end describe "when retrieving a catalog" do before do Puppet.settings.stubs(:use).returns(true) @agent.stubs(:facts_for_uploading).returns({}) @catalog = Puppet::Resource::Catalog.new # this is the default when using a Configurer instance Puppet::Resource::Catalog.indirection.stubs(:terminus_class).returns :rest @agent.stubs(:convert_catalog).returns @catalog end describe "and configured to only retrieve a catalog from the cache" do before do Puppet.settings[:use_cached_catalog] = true end it "should first look in the cache for a catalog" do Puppet::Resource::Catalog.indirection.expects(:find).with { |name, options| options[:ignore_terminus] == true }.returns @catalog Puppet::Resource::Catalog.indirection.expects(:find).with { |name, options| options[:ignore_cache] == true }.never @agent.retrieve_catalog({}).should == @catalog end it "should compile a new catalog if none is found in the cache" do Puppet::Resource::Catalog.indirection.expects(:find).with { |name, options| options[:ignore_terminus] == true }.returns nil Puppet::Resource::Catalog.indirection.expects(:find).with { |name, options| options[:ignore_cache] == true }.returns @catalog @agent.retrieve_catalog({}).should == @catalog end end it "should use the Catalog class to get its catalog" do Puppet::Resource::Catalog.indirection.expects(:find).returns @catalog @agent.retrieve_catalog({}) end it "should use its node_name_value to retrieve the catalog" do Facter.stubs(:value).returns "eh" Puppet.settings[:node_name_value] = "myhost.domain.com" Puppet::Resource::Catalog.indirection.expects(:find).with { |name, options| name == "myhost.domain.com" }.returns @catalog @agent.retrieve_catalog({}) end it "should default to returning a catalog retrieved directly from the server, skipping the cache" do Puppet::Resource::Catalog.indirection.expects(:find).with { |name, options| options[:ignore_cache] == true }.returns @catalog @agent.retrieve_catalog({}).should == @catalog end it "should log and return the cached catalog when no catalog can be retrieved from the server" do Puppet::Resource::Catalog.indirection.expects(:find).with { |name, options| options[:ignore_cache] == true }.returns nil Puppet::Resource::Catalog.indirection.expects(:find).with { |name, options| options[:ignore_terminus] == true }.returns @catalog Puppet.expects(:notice) @agent.retrieve_catalog({}).should == @catalog end it "should not look in the cache for a catalog if one is returned from the server" do Puppet::Resource::Catalog.indirection.expects(:find).with { |name, options| options[:ignore_cache] == true }.returns @catalog Puppet::Resource::Catalog.indirection.expects(:find).with { |name, options| options[:ignore_terminus] == true }.never @agent.retrieve_catalog({}).should == @catalog end it "should return the cached catalog when retrieving the remote catalog throws an exception" do Puppet::Resource::Catalog.indirection.expects(:find).with { |name, options| options[:ignore_cache] == true }.raises "eh" Puppet::Resource::Catalog.indirection.expects(:find).with { |name, options| options[:ignore_terminus] == true }.returns @catalog @agent.retrieve_catalog({}).should == @catalog end it "should log and return nil if no catalog can be retrieved from the server and :usecacheonfailure is disabled" do Puppet[:usecacheonfailure] = false Puppet::Resource::Catalog.indirection.expects(:find).with { |name, options| options[:ignore_cache] == true }.returns nil Puppet.expects(:warning) @agent.retrieve_catalog({}).should be_nil end it "should return nil if no cached catalog is available and no catalog can be retrieved from the server" do Puppet::Resource::Catalog.indirection.expects(:find).with { |name, options| options[:ignore_cache] == true }.returns nil Puppet::Resource::Catalog.indirection.expects(:find).with { |name, options| options[:ignore_terminus] == true }.returns nil @agent.retrieve_catalog({}).should be_nil end it "should convert the catalog before returning" do Puppet::Resource::Catalog.indirection.stubs(:find).returns @catalog @agent.expects(:convert_catalog).with { |cat, dur| cat == @catalog }.returns "converted catalog" @agent.retrieve_catalog({}).should == "converted catalog" end it "should return nil if there is an error while retrieving the catalog" do Puppet::Resource::Catalog.indirection.expects(:find).at_least_once.raises "eh" @agent.retrieve_catalog({}).should be_nil end end describe "when converting the catalog" do before do Puppet.settings.stubs(:use).returns(true) @catalog = Puppet::Resource::Catalog.new @oldcatalog = stub 'old_catalog', :to_ral => @catalog end it "should convert the catalog to a RAL-formed catalog" do @oldcatalog.expects(:to_ral).returns @catalog @agent.convert_catalog(@oldcatalog, 10).should equal(@catalog) end it "should finalize the catalog" do @catalog.expects(:finalize) @agent.convert_catalog(@oldcatalog, 10) end it "should record the passed retrieval time with the RAL catalog" do @catalog.expects(:retrieval_duration=).with 10 @agent.convert_catalog(@oldcatalog, 10) end it "should write the RAL catalog's class file" do @catalog.expects(:write_class_file) @agent.convert_catalog(@oldcatalog, 10) end it "should write the RAL catalog's resource file" do @catalog.expects(:write_resource_file) @agent.convert_catalog(@oldcatalog, 10) end end end diff --git a/spec/unit/confine/exists_spec.rb b/spec/unit/confine/exists_spec.rb index a2a575bd2..0e1dba326 100755 --- a/spec/unit/confine/exists_spec.rb +++ b/spec/unit/confine/exists_spec.rb @@ -1,80 +1,80 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/confine/exists' describe Puppet::Confine::Exists do before do @confine = Puppet::Confine::Exists.new("/my/file") @confine.label = "eh" end it "should be named :exists" do Puppet::Confine::Exists.name.should == :exists end it "should not pass if exists is nil" do confine = Puppet::Confine::Exists.new(nil) confine.label = ":exists => nil" confine.expects(:pass?).with(nil) confine.should_not be_valid end it "should use the 'pass?' method to test validity" do @confine.expects(:pass?).with("/my/file") @confine.valid? end it "should return false if the value is false" do - @confine.pass?(false).should be_false + @confine.pass?(false).should be_falsey end it "should return false if the value does not point to a file" do Puppet::FileSystem.expects(:exist?).with("/my/file").returns false - @confine.pass?("/my/file").should be_false + @confine.pass?("/my/file").should be_falsey end it "should return true if the value points to a file" do Puppet::FileSystem.expects(:exist?).with("/my/file").returns true - @confine.pass?("/my/file").should be_true + @confine.pass?("/my/file").should be_truthy end it "should produce a message saying that a file is missing" do @confine.message("/my/file").should be_include("does not exist") end describe "and the confine is for binaries" do before { @confine.stubs(:for_binary).returns true } it "should use its 'which' method to look up the full path of the file" do @confine.expects(:which).returns nil @confine.pass?("/my/file") end it "should return false if no executable can be found" do @confine.expects(:which).with("/my/file").returns nil - @confine.pass?("/my/file").should be_false + @confine.pass?("/my/file").should be_falsey end it "should return true if the executable can be found" do @confine.expects(:which).with("/my/file").returns "/my/file" - @confine.pass?("/my/file").should be_true + @confine.pass?("/my/file").should be_truthy end end it "should produce a summary containing all missing files" do Puppet::FileSystem.stubs(:exist?).returns true Puppet::FileSystem.expects(:exist?).with("/two").returns false Puppet::FileSystem.expects(:exist?).with("/four").returns false confine = Puppet::Confine::Exists.new %w{/one /two /three /four} confine.summary.should == %w{/two /four} end it "should summarize multiple instances by returning a flattened array of their summaries" do c1 = mock '1', :summary => %w{one} c2 = mock '2', :summary => %w{two} c3 = mock '3', :summary => %w{three} Puppet::Confine::Exists.summarize([c1, c2, c3]).should == %w{one two three} end end diff --git a/spec/unit/confine/false_spec.rb b/spec/unit/confine/false_spec.rb index 44ecd40ed..f99aa183c 100755 --- a/spec/unit/confine/false_spec.rb +++ b/spec/unit/confine/false_spec.rb @@ -1,52 +1,52 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/confine/false' describe Puppet::Confine::False do it "should be named :false" do Puppet::Confine::False.name.should == :false end it "should require a value" do lambda { Puppet::Confine.new }.should raise_error(ArgumentError) end describe "when testing values" do before { @confine = Puppet::Confine::False.new("foo") } it "should use the 'pass?' method to test validity" do @confine = Puppet::Confine::False.new("foo") @confine.label = "eh" @confine.expects(:pass?).with("foo") @confine.valid? end it "should return true if the value is false" do - @confine.pass?(false).should be_true + @confine.pass?(false).should be_truthy end it "should return false if the value is not false" do - @confine.pass?("else").should be_false + @confine.pass?("else").should be_falsey end it "should produce a message that a value is true" do @confine = Puppet::Confine::False.new("foo") @confine.message("eh").should be_include("true") end end it "should be able to produce a summary with the number of incorrectly true values" do confine = Puppet::Confine::False.new %w{one two three four} confine.expects(:pass?).times(4).returns(true).returns(false).returns(true).returns(false) confine.summary.should == 2 end it "should summarize multiple instances by summing their summaries" do c1 = mock '1', :summary => 1 c2 = mock '2', :summary => 2 c3 = mock '3', :summary => 3 Puppet::Confine::False.summarize([c1, c2, c3]).should == 6 end end diff --git a/spec/unit/confine/feature_spec.rb b/spec/unit/confine/feature_spec.rb index dad9d9b8b..47f961659 100755 --- a/spec/unit/confine/feature_spec.rb +++ b/spec/unit/confine/feature_spec.rb @@ -1,57 +1,57 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/confine/feature' describe Puppet::Confine::Feature do it "should be named :feature" do Puppet::Confine::Feature.name.should == :feature end it "should require a value" do lambda { Puppet::Confine::Feature.new }.should raise_error(ArgumentError) end it "should always convert values to an array" do Puppet::Confine::Feature.new("/some/file").values.should be_instance_of(Array) end describe "when testing values" do before do @confine = Puppet::Confine::Feature.new("myfeature") @confine.label = "eh" end it "should use the Puppet features instance to test validity" do Puppet.features.expects(:myfeature?) @confine.valid? end it "should return true if the feature is present" do Puppet.features.add(:myfeature) do true end - @confine.pass?("myfeature").should be_true + @confine.pass?("myfeature").should be_truthy end it "should return false if the value is false" do Puppet.features.add(:myfeature) do false end - @confine.pass?("myfeature").should be_false + @confine.pass?("myfeature").should be_falsey end it "should log that a feature is missing" do @confine.message("myfeat").should be_include("missing") end end it "should summarize multiple instances by returning a flattened array of all missing features" do confines = [] confines << Puppet::Confine::Feature.new(%w{one two}) confines << Puppet::Confine::Feature.new(%w{two}) confines << Puppet::Confine::Feature.new(%w{three four}) features = mock 'feature' features.stub_everything Puppet.stubs(:features).returns features Puppet::Confine::Feature.summarize(confines).sort.should == %w{one two three four}.sort end end diff --git a/spec/unit/confine/true_spec.rb b/spec/unit/confine/true_spec.rb index d7484d59c..cc57cd8e5 100755 --- a/spec/unit/confine/true_spec.rb +++ b/spec/unit/confine/true_spec.rb @@ -1,52 +1,52 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/confine/true' describe Puppet::Confine::True do it "should be named :true" do Puppet::Confine::True.name.should == :true end it "should require a value" do lambda { Puppet::Confine::True.new }.should raise_error(ArgumentError) end describe "when testing values" do before do @confine = Puppet::Confine::True.new("foo") @confine.label = "eh" end it "should use the 'pass?' method to test validity" do @confine.expects(:pass?).with("foo") @confine.valid? end it "should return true if the value is not false" do - @confine.pass?("else").should be_true + @confine.pass?("else").should be_truthy end it "should return false if the value is false" do - @confine.pass?(nil).should be_false + @confine.pass?(nil).should be_falsey end it "should produce the message that a value is false" do @confine.message("eh").should be_include("false") end end it "should produce the number of false values when asked for a summary" do @confine = Puppet::Confine::True.new %w{one two three four} @confine.expects(:pass?).times(4).returns(true).returns(false).returns(true).returns(false) @confine.summary.should == 2 end it "should summarize multiple instances by summing their summaries" do c1 = mock '1', :summary => 1 c2 = mock '2', :summary => 2 c3 = mock '3', :summary => 3 Puppet::Confine::True.summarize([c1, c2, c3]).should == 6 end end diff --git a/spec/unit/defaults_spec.rb b/spec/unit/defaults_spec.rb index 0d141d91c..b0ab569f3 100644 --- a/spec/unit/defaults_spec.rb +++ b/spec/unit/defaults_spec.rb @@ -1,74 +1,74 @@ require 'spec_helper' require 'puppet/settings' describe "Defaults" do describe ".default_diffargs" do describe "on AIX" do before(:each) do Facter.stubs(:value).with(:kernel).returns("AIX") end describe "on 5.3" do before(:each) do Facter.stubs(:value).with(:kernelmajversion).returns("5300") end it "should be empty" do Puppet.default_diffargs.should == "" end end [ "", nil, "6300", "7300", ].each do |kernel_version| describe "on kernel version #{kernel_version.inspect}" do before(:each) do Facter.stubs(:value).with(:kernelmajversion).returns(kernel_version) end it "should be '-u'" do Puppet.default_diffargs.should == "-u" end end end end describe "on everything else" do before(:each) do Facter.stubs(:value).with(:kernel).returns("NOT_AIX") end it "should be '-u'" do Puppet.default_diffargs.should == "-u" end end end describe 'cfacter' do before :each do Facter.reset end it 'should default to false' do - Puppet.settings[:cfacter].should be_false + Puppet.settings[:cfacter].should be_falsey end it 'should raise an error if cfacter is not installed' do Puppet.features.stubs(:cfacter?).returns false lambda { Puppet.settings[:cfacter] = true }.should raise_exception ArgumentError, 'cfacter version 0.2.0 or later is not installed.' end it 'should raise an error if facter has already evaluated facts' do Facter[:facterversion] Puppet.features.stubs(:cfacter?).returns true lambda { Puppet.settings[:cfacter] = true }.should raise_exception ArgumentError, 'facter has already evaluated facts.' end it 'should initialize cfacter when set to true' do Puppet.features.stubs(:cfacter?).returns true CFacter = mock CFacter.stubs(:initialize) Puppet.settings[:cfacter] = true end end end diff --git a/spec/unit/file_serving/base_spec.rb b/spec/unit/file_serving/base_spec.rb index 5b9794211..2e6f9bf21 100755 --- a/spec/unit/file_serving/base_spec.rb +++ b/spec/unit/file_serving/base_spec.rb @@ -1,168 +1,168 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/file_serving/base' describe Puppet::FileServing::Base do let(:path) { File.expand_path('/module/dir/file') } let(:file) { File.expand_path('/my/file') } it "should accept a path" do Puppet::FileServing::Base.new(path).path.should == path end it "should require that paths be fully qualified" do lambda { Puppet::FileServing::Base.new("module/dir/file") }.should raise_error(ArgumentError) end it "should allow specification of whether links should be managed" do Puppet::FileServing::Base.new(path, :links => :manage).links.should == :manage end it "should have a :source attribute" do file = Puppet::FileServing::Base.new(path) file.should respond_to(:source) file.should respond_to(:source=) end it "should consider :ignore links equivalent to :manage links" do Puppet::FileServing::Base.new(path, :links => :ignore).links.should == :manage end it "should fail if :links is set to anything other than :manage, :follow, or :ignore" do proc { Puppet::FileServing::Base.new(path, :links => :else) }.should raise_error(ArgumentError) end it "should allow links values to be set as strings" do Puppet::FileServing::Base.new(path, :links => "follow").links.should == :follow end it "should default to :manage for :links" do Puppet::FileServing::Base.new(path).links.should == :manage end it "should allow specification of a path" do Puppet::FileSystem.stubs(:exist?).returns(true) Puppet::FileServing::Base.new(path, :path => file).path.should == file end it "should allow specification of a relative path" do Puppet::FileSystem.stubs(:exist?).returns(true) Puppet::FileServing::Base.new(path, :relative_path => "my/file").relative_path.should == "my/file" end it "should have a means of determining if the file exists" do Puppet::FileServing::Base.new(file).should respond_to(:exist?) end it "should correctly indicate if the file is present" do Puppet::FileSystem.expects(:lstat).with(file).returns stub('stat') - Puppet::FileServing::Base.new(file).exist?.should be_true + Puppet::FileServing::Base.new(file).exist?.should be_truthy end it "should correctly indicate if the file is absent" do Puppet::FileSystem.expects(:lstat).with(file).raises RuntimeError - Puppet::FileServing::Base.new(file).exist?.should be_false + Puppet::FileServing::Base.new(file).exist?.should be_falsey end describe "when setting the relative path" do it "should require that the relative path be unqualified" do @file = Puppet::FileServing::Base.new(path) Puppet::FileSystem.stubs(:exist?).returns(true) proc { @file.relative_path = File.expand_path("/qualified/file") }.should raise_error(ArgumentError) end end describe "when determining the full file path" do let(:path) { File.expand_path('/this/file') } let(:file) { Puppet::FileServing::Base.new(path) } it "should return the path if there is no relative path" do file.full_path.should == path end it "should return the path if the relative_path is set to ''" do file.relative_path = "" file.full_path.should == path end it "should return the path if the relative_path is set to '.'" do file.relative_path = "." file.full_path.should == path end it "should return the path joined with the relative path if there is a relative path and it is not set to '/' or ''" do file.relative_path = "not/qualified" file.full_path.should == File.join(path, "not/qualified") end it "should strip extra slashes" do file = Puppet::FileServing::Base.new(File.join(File.expand_path('/'), "//this//file")) file.full_path.should == path end end describe "when handling a UNC file path on Windows" do let(:path) { '//server/share/filename' } let(:file) { Puppet::FileServing::Base.new(path) } it "should preserve double slashes at the beginning of the path" do Puppet.features.stubs(:microsoft_windows?).returns(true) file.full_path.should == path end it "should strip double slashes not at the beginning of the path" do Puppet.features.stubs(:microsoft_windows?).returns(true) file = Puppet::FileServing::Base.new('//server//share//filename') file.full_path.should == path end end describe "when stat'ing files" do let(:path) { File.expand_path('/this/file') } let(:file) { Puppet::FileServing::Base.new(path) } let(:stat) { stub('stat', :ftype => 'file' ) } let(:stubbed_file) { stub(path, :stat => stat, :lstat => stat)} it "should stat the file's full path" do Puppet::FileSystem.expects(:lstat).with(path).returns stat file.stat end it "should fail if the file does not exist" do Puppet::FileSystem.expects(:lstat).with(path).raises(Errno::ENOENT) proc { file.stat }.should raise_error(Errno::ENOENT) end it "should use :lstat if :links is set to :manage" do Puppet::FileSystem.expects(:lstat).with(path).returns stubbed_file file.stat end it "should use :stat if :links is set to :follow" do Puppet::FileSystem.expects(:stat).with(path).returns stubbed_file file.links = :follow file.stat end end describe "#absolute?" do it "should be accept POSIX paths" do Puppet::FileServing::Base.should be_absolute('/') end it "should accept Windows paths on Windows" do Puppet.features.stubs(:microsoft_windows?).returns(true) Puppet.features.stubs(:posix?).returns(false) Puppet::FileServing::Base.should be_absolute('c:/foo') end it "should reject Windows paths on POSIX" do Puppet.features.stubs(:microsoft_windows?).returns(false) Puppet::FileServing::Base.should_not be_absolute('c:/foo') end end end diff --git a/spec/unit/file_serving/configuration_spec.rb b/spec/unit/file_serving/configuration_spec.rb index a2c808051..06611a58d 100755 --- a/spec/unit/file_serving/configuration_spec.rb +++ b/spec/unit/file_serving/configuration_spec.rb @@ -1,231 +1,231 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/file_serving/configuration' describe Puppet::FileServing::Configuration do include PuppetSpec::Files before :each do @path = make_absolute("/path/to/configuration/file.conf") Puppet[:trace] = false Puppet[:fileserverconfig] = @path end after :each do Puppet::FileServing::Configuration.instance_variable_set(:@configuration, nil) end it "should make :new a private method" do expect { Puppet::FileServing::Configuration.new }.to raise_error end it "should return the same configuration each time 'configuration' is called" do Puppet::FileServing::Configuration.configuration.should equal(Puppet::FileServing::Configuration.configuration) end describe "when initializing" do it "should work without a configuration file" do Puppet::FileSystem.stubs(:exist?).with(@path).returns(false) expect { Puppet::FileServing::Configuration.configuration }.to_not raise_error end it "should parse the configuration file if present" do Puppet::FileSystem.stubs(:exist?).with(@path).returns(true) @parser = mock 'parser' @parser.expects(:parse).returns({}) Puppet::FileServing::Configuration::Parser.stubs(:new).returns(@parser) Puppet::FileServing::Configuration.configuration end it "should determine the path to the configuration file from the Puppet settings" do Puppet::FileServing::Configuration.configuration end end describe "when parsing the configuration file" do before do Puppet::FileSystem.stubs(:exist?).with(@path).returns(true) @parser = mock 'parser' Puppet::FileServing::Configuration::Parser.stubs(:new).returns(@parser) end it "should set the mount list to the results of parsing" do @parser.expects(:parse).returns("one" => mock("mount")) config = Puppet::FileServing::Configuration.configuration - config.mounted?("one").should be_true + config.mounted?("one").should be_truthy end it "should not raise exceptions" do @parser.expects(:parse).raises(ArgumentError) expect { Puppet::FileServing::Configuration.configuration }.to_not raise_error end it "should replace the existing mount list with the results of reparsing" do @parser.expects(:parse).returns("one" => mock("mount")) config = Puppet::FileServing::Configuration.configuration - config.mounted?("one").should be_true + config.mounted?("one").should be_truthy # Now parse again @parser.expects(:parse).returns("two" => mock('other')) config.send(:readconfig, false) - config.mounted?("one").should be_false - config.mounted?("two").should be_true + config.mounted?("one").should be_falsey + config.mounted?("two").should be_truthy end it "should not replace the mount list until the file is entirely parsed successfully" do @parser.expects(:parse).returns("one" => mock("mount")) @parser.expects(:parse).raises(ArgumentError) config = Puppet::FileServing::Configuration.configuration # Now parse again, so the exception gets thrown config.send(:readconfig, false) - config.mounted?("one").should be_true + config.mounted?("one").should be_truthy end it "should add modules and plugins mounts even if the file does not exist" do Puppet::FileSystem.expects(:exist?).returns false # the file doesn't exist config = Puppet::FileServing::Configuration.configuration - config.mounted?("modules").should be_true - config.mounted?("plugins").should be_true + config.mounted?("modules").should be_truthy + config.mounted?("plugins").should be_truthy end it "should allow all access to modules and plugins if no fileserver.conf exists" do Puppet::FileSystem.expects(:exist?).returns false # the file doesn't exist modules = stub 'modules', :empty? => true Puppet::FileServing::Mount::Modules.stubs(:new).returns(modules) modules.expects(:allow).with('*') plugins = stub 'plugins', :empty? => true Puppet::FileServing::Mount::Plugins.stubs(:new).returns(plugins) plugins.expects(:allow).with('*') Puppet::FileServing::Configuration.configuration end it "should not allow access from all to modules and plugins if the fileserver.conf provided some rules" do Puppet::FileSystem.expects(:exist?).returns false # the file doesn't exist modules = stub 'modules', :empty? => false Puppet::FileServing::Mount::Modules.stubs(:new).returns(modules) modules.expects(:allow).with('*').never plugins = stub 'plugins', :empty? => false Puppet::FileServing::Mount::Plugins.stubs(:new).returns(plugins) plugins.expects(:allow).with('*').never Puppet::FileServing::Configuration.configuration end it "should add modules and plugins mounts even if they are not returned by the parser" do @parser.expects(:parse).returns("one" => mock("mount")) Puppet::FileSystem.expects(:exist?).returns true # the file doesn't exist config = Puppet::FileServing::Configuration.configuration - config.mounted?("modules").should be_true - config.mounted?("plugins").should be_true + config.mounted?("modules").should be_truthy + config.mounted?("plugins").should be_truthy end end describe "when finding the specified mount" do it "should choose the named mount if one exists" do config = Puppet::FileServing::Configuration.configuration config.expects(:mounts).returns("one" => "foo") config.find_mount("one", mock('env')).should == "foo" end it "should return nil if there is no such named mount" do config = Puppet::FileServing::Configuration.configuration env = mock 'environment' mount = mock 'mount' config.stubs(:mounts).returns("modules" => mount) config.find_mount("foo", env).should be_nil end end describe "#split_path" do let(:config) { Puppet::FileServing::Configuration.configuration } let(:request) { stub 'request', :key => "foo/bar/baz", :options => {}, :node => nil, :environment => mock("env") } before do config.stubs(:find_mount) end it "should reread the configuration" do config.expects(:readconfig) config.split_path(request) end it "should treat the first field of the URI path as the mount name" do config.expects(:find_mount).with { |name, node| name == "foo" } config.split_path(request) end it "should fail if the mount name is not alpha-numeric" do request.expects(:key).returns "foo&bar/asdf" expect { config.split_path(request) }.to raise_error(ArgumentError) end it "should support dashes in the mount name" do request.expects(:key).returns "foo-bar/asdf" expect { config.split_path(request) }.to_not raise_error end it "should use the mount name and environment to find the mount" do config.expects(:find_mount).with { |name, env| name == "foo" and env == request.environment } request.stubs(:node).returns("mynode") config.split_path(request) end it "should return nil if the mount cannot be found" do config.expects(:find_mount).returns nil config.split_path(request).should be_nil end it "should return the mount and the relative path if the mount is found" do mount = stub 'mount', :name => "foo" config.expects(:find_mount).returns mount config.split_path(request).should == [mount, "bar/baz"] end it "should remove any double slashes" do request.stubs(:key).returns "foo/bar//baz" mount = stub 'mount', :name => "foo" config.expects(:find_mount).returns mount config.split_path(request).should == [mount, "bar/baz"] end it "should fail if the path contains .." do request.stubs(:key).returns 'module/foo/../../bar' expect do config.split_path(request) end.to raise_error(ArgumentError, /Invalid relative path/) end it "should return the relative path as nil if it is an empty string" do request.expects(:key).returns "foo" mount = stub 'mount', :name => "foo" config.expects(:find_mount).returns mount config.split_path(request).should == [mount, nil] end it "should add 'modules/' to the relative path if the modules mount is used but not specified, for backward compatibility" do request.expects(:key).returns "foo/bar" mount = stub 'mount', :name => "modules" config.expects(:find_mount).returns mount config.split_path(request).should == [mount, "foo/bar"] end end end diff --git a/spec/unit/file_serving/fileset_spec.rb b/spec/unit/file_serving/fileset_spec.rb index e4df5c93d..1473021dc 100755 --- a/spec/unit/file_serving/fileset_spec.rb +++ b/spec/unit/file_serving/fileset_spec.rb @@ -1,354 +1,354 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/file_serving/fileset' describe Puppet::FileServing::Fileset do include PuppetSpec::Files let(:somefile) { make_absolute("/some/file") } context "when initializing" do it "requires a path" do expect { Puppet::FileServing::Fileset.new }.to raise_error(ArgumentError) end it "fails if its path is not fully qualified" do expect { Puppet::FileServing::Fileset.new("some/file") }.to raise_error(ArgumentError, "Fileset paths must be fully qualified: some/file") end it "removes a trailing file path separator" do path_with_separator = "#{somefile}#{File::SEPARATOR}" Puppet::FileSystem.expects(:lstat).with(somefile).returns stub('stat') fileset = Puppet::FileServing::Fileset.new(path_with_separator) fileset.path.should == somefile end it "can be created from the root directory" do path = File.expand_path(File::SEPARATOR) Puppet::FileSystem.expects(:lstat).with(path).returns stub('stat') fileset = Puppet::FileServing::Fileset.new(path) fileset.path.should == path end it "fails if its path does not exist" do Puppet::FileSystem.expects(:lstat).with(somefile).raises(Errno::ENOENT) expect { Puppet::FileServing::Fileset.new(somefile) }.to raise_error(ArgumentError, "Fileset paths must exist") end it "accepts a 'recurse' option" do Puppet::FileSystem.expects(:lstat).with(somefile).returns stub('stat') set = Puppet::FileServing::Fileset.new(somefile, :recurse => true) - set.recurse.should be_true + set.recurse.should be_truthy end it "accepts a 'recurselimit' option" do Puppet::FileSystem.expects(:lstat).with(somefile).returns stub('stat') set = Puppet::FileServing::Fileset.new(somefile, :recurselimit => 3) set.recurselimit.should == 3 end it "accepts an 'ignore' option" do Puppet::FileSystem.expects(:lstat).with(somefile).returns stub('stat') set = Puppet::FileServing::Fileset.new(somefile, :ignore => ".svn") set.ignore.should == [".svn"] end it "accepts a 'links' option" do Puppet::FileSystem.expects(:lstat).with(somefile).returns stub('stat') set = Puppet::FileServing::Fileset.new(somefile, :links => :manage) set.links.should == :manage end it "accepts a 'checksum_type' option" do Puppet::FileSystem.expects(:lstat).with(somefile).returns stub('stat') set = Puppet::FileServing::Fileset.new(somefile, :checksum_type => :test) set.checksum_type.should == :test end it "fails if 'links' is set to anything other than :manage or :follow" do expect { Puppet::FileServing::Fileset.new(somefile, :links => :whatever) }.to raise_error(ArgumentError, "Invalid :links value 'whatever'") end it "defaults to 'false' for recurse" do Puppet::FileSystem.expects(:lstat).with(somefile).returns stub('stat') Puppet::FileServing::Fileset.new(somefile).recurse.should == false end it "defaults to :infinite for recurselimit" do Puppet::FileSystem.expects(:lstat).with(somefile).returns stub('stat') Puppet::FileServing::Fileset.new(somefile).recurselimit.should == :infinite end it "defaults to an empty ignore list" do Puppet::FileSystem.expects(:lstat).with(somefile).returns stub('stat') Puppet::FileServing::Fileset.new(somefile).ignore.should == [] end it "defaults to :manage for links" do Puppet::FileSystem.expects(:lstat).with(somefile).returns stub('stat') Puppet::FileServing::Fileset.new(somefile).links.should == :manage end describe "using an indirector request" do let(:values) { { :links => :manage, :ignore => %w{a b}, :recurse => true, :recurselimit => 1234 } } let(:stub_file) { stub(somefile, :lstat => stub('stat')) } before :each do Puppet::FileSystem.expects(:lstat).with(somefile).returns stub('stat') end [:recurse, :recurselimit, :ignore, :links].each do |option| it "passes the #{option} option on to the fileset if present" do request = Puppet::Indirector::Request.new(:file_serving, :find, "foo", nil, {option => values[option]}) Puppet::FileServing::Fileset.new(somefile, request).send(option).should == values[option] end end it "converts the integer as a string to their integer counterpart when setting options" do request = Puppet::Indirector::Request.new(:file_serving, :find, "foo", nil, {:recurselimit => "1234"}) Puppet::FileServing::Fileset.new(somefile, request).recurselimit.should == 1234 end it "converts the string 'true' to the boolean true when setting options" do request = Puppet::Indirector::Request.new(:file_serving, :find, "foo", nil, {:recurse => "true"}) Puppet::FileServing::Fileset.new(somefile, request).recurse.should == true end it "converts the string 'false' to the boolean false when setting options" do request = Puppet::Indirector::Request.new(:file_serving, :find, "foo", nil, {:recurse => "false"}) Puppet::FileServing::Fileset.new(somefile, request).recurse.should == false end end end context "when recursing" do before do @path = make_absolute("/my/path") Puppet::FileSystem.stubs(:lstat).with(@path).returns stub('stat', :directory? => true) @fileset = Puppet::FileServing::Fileset.new(@path) @dirstat = stub 'dirstat', :directory? => true @filestat = stub 'filestat', :directory? => false end def mock_dir_structure(path, stat_method = :lstat) Puppet::FileSystem.stubs(stat_method).with(path).returns @dirstat # Keep track of the files we're stubbing. @files = %w{.} top_names = %w{one two .svn CVS} sub_names = %w{file1 file2 .svn CVS 0 false} Dir.stubs(:entries).with(path).returns(top_names) top_names.each do |subdir| @files << subdir # relative path subpath = File.join(path, subdir) Puppet::FileSystem.stubs(stat_method).with(subpath).returns @dirstat Dir.stubs(:entries).with(subpath).returns(sub_names) sub_names.each do |file| @files << File.join(subdir, file) # relative path subfile_path = File.join(subpath, file) Puppet::FileSystem.stubs(stat_method).with(subfile_path).returns(@filestat) end end end MockStat = Struct.new(:path, :directory) do # struct doesn't support thing ending in ? def directory? directory end end MockDirectory = Struct.new(:name, :entries) do def mock(base_path) extend Mocha::API path = File.join(base_path, name) Puppet::FileSystem.stubs(:lstat).with(path).returns MockStat.new(path, true) Dir.stubs(:entries).with(path).returns(['.', '..'] + entries.map(&:name)) entries.each do |entry| entry.mock(path) end end end MockFile = Struct.new(:name) do def mock(base_path) extend Mocha::API path = File.join(base_path, name) Puppet::FileSystem.stubs(:lstat).with(path).returns MockStat.new(path, false) end end it "doesn't ignore pending directories when the last entry at the top level is a file" do structure = MockDirectory.new('path', [MockDirectory.new('dir1', [MockDirectory.new('a', [MockFile.new('f')])]), MockFile.new('file')]) structure.mock(make_absolute('/your')) fileset = Puppet::FileServing::Fileset.new(make_absolute('/your/path')) fileset.recurse = true fileset.links = :manage fileset.files.should == [".", "dir1", "file", "dir1/a", "dir1/a/f"] end it "recurses through the whole file tree if :recurse is set to 'true'" do mock_dir_structure(@path) @fileset.recurse = true @fileset.files.sort.should == @files.sort end it "does not recurse if :recurse is set to 'false'" do mock_dir_structure(@path) @fileset.recurse = false @fileset.files.should == %w{.} end it "recurses to the level set by :recurselimit" do mock_dir_structure(@path) @fileset.recurse = true @fileset.recurselimit = 1 @fileset.files.should == %w{. one two .svn CVS} end it "ignores the '.' and '..' directories in subdirectories" do mock_dir_structure(@path) @fileset.recurse = true @fileset.files.sort.should == @files.sort end it "does not fail if the :ignore value provided is nil" do mock_dir_structure(@path) @fileset.recurse = true @fileset.ignore = nil expect { @fileset.files }.to_not raise_error end it "ignores files that match a single pattern in the ignore list" do mock_dir_structure(@path) @fileset.recurse = true @fileset.ignore = ".svn" @fileset.files.find { |file| file.include?(".svn") }.should be_nil end it "ignores files that match any of multiple patterns in the ignore list" do mock_dir_structure(@path) @fileset.recurse = true @fileset.ignore = %w{.svn CVS} @fileset.files.find { |file| file.include?(".svn") or file.include?("CVS") }.should be_nil end it "ignores files that match a pattern given as a number" do mock_dir_structure(@path) @fileset.recurse = true @fileset.ignore = [0] @fileset.files.find { |file| file.include?("0") }.should be_nil end it "ignores files that match a pattern given as a boolean" do mock_dir_structure(@path) @fileset.recurse = true @fileset.ignore = [false] @fileset.files.find { |file| file.include?("false") }.should be_nil end it "uses Puppet::FileSystem#stat if :links is set to :follow" do mock_dir_structure(@path, :stat) @fileset.recurse = true @fileset.links = :follow @fileset.files.sort.should == @files.sort end it "uses Puppet::FileSystem#lstat if :links is set to :manage" do mock_dir_structure(@path, :lstat) @fileset.recurse = true @fileset.links = :manage @fileset.files.sort.should == @files.sort end it "works when paths have regexp significant characters" do @path = make_absolute("/my/path/rV1x2DafFr0R6tGG+1bbk++++TM") stat = stub('dir_stat', :directory? => true) stub_file = stub(@path, :stat => stat, :lstat => stat) Puppet::FileSystem.expects(:lstat).with(@path).returns stub(@path, :stat => stat, :lstat => stat) @fileset = Puppet::FileServing::Fileset.new(@path) mock_dir_structure(@path) @fileset.recurse = true @fileset.files.sort.should == @files.sort end end it "manages the links to missing files" do path = make_absolute("/my/path") stat = stub 'stat', :directory? => true Puppet::FileSystem.expects(:stat).with(path).returns stat Puppet::FileSystem.expects(:lstat).with(path).returns stat link_path = File.join(path, "mylink") Puppet::FileSystem.expects(:stat).with(link_path).raises(Errno::ENOENT) Dir.stubs(:entries).with(path).returns(["mylink"]) fileset = Puppet::FileServing::Fileset.new(path) fileset.links = :follow fileset.recurse = true fileset.files.sort.should == %w{. mylink}.sort end context "when merging other filesets" do before do @paths = [make_absolute("/first/path"), make_absolute("/second/path"), make_absolute("/third/path")] Puppet::FileSystem.stubs(:lstat).returns stub('stat', :directory? => false) @filesets = @paths.collect do |path| Puppet::FileSystem.stubs(:lstat).with(path).returns stub('stat', :directory? => true) Puppet::FileServing::Fileset.new(path, :recurse => true) end Dir.stubs(:entries).returns [] end it "returns a hash of all files in each fileset with the value being the base path" do Dir.expects(:entries).with(make_absolute("/first/path")).returns(%w{one uno}) Dir.expects(:entries).with(make_absolute("/second/path")).returns(%w{two dos}) Dir.expects(:entries).with(make_absolute("/third/path")).returns(%w{three tres}) Puppet::FileServing::Fileset.merge(*@filesets).should == { "." => make_absolute("/first/path"), "one" => make_absolute("/first/path"), "uno" => make_absolute("/first/path"), "two" => make_absolute("/second/path"), "dos" => make_absolute("/second/path"), "three" => make_absolute("/third/path"), "tres" => make_absolute("/third/path"), } end it "includes the base directory from the first fileset" do Dir.expects(:entries).with(make_absolute("/first/path")).returns(%w{one}) Dir.expects(:entries).with(make_absolute("/second/path")).returns(%w{two}) Puppet::FileServing::Fileset.merge(*@filesets)["."].should == make_absolute("/first/path") end it "uses the base path of the first found file when relative file paths conflict" do Dir.expects(:entries).with(make_absolute("/first/path")).returns(%w{one}) Dir.expects(:entries).with(make_absolute("/second/path")).returns(%w{one}) Puppet::FileServing::Fileset.merge(*@filesets)["one"].should == make_absolute("/first/path") end end end diff --git a/spec/unit/file_system/uniquefile_spec.rb b/spec/unit/file_system/uniquefile_spec.rb index 1268581fa..724f5b0e5 100644 --- a/spec/unit/file_system/uniquefile_spec.rb +++ b/spec/unit/file_system/uniquefile_spec.rb @@ -1,184 +1,184 @@ require 'spec_helper' describe Puppet::FileSystem::Uniquefile do it "makes the name of the file available" do Puppet::FileSystem::Uniquefile.open_tmp('foo') do |file| expect(file.path).to match(/foo/) end end it "provides a writeable file" do Puppet::FileSystem::Uniquefile.open_tmp('foo') do |file| file.write("stuff") file.flush expect(Puppet::FileSystem.read(file.path)).to eq("stuff") end end it "returns the value of the block" do the_value = Puppet::FileSystem::Uniquefile.open_tmp('foo') do |file| "my value" end expect(the_value).to eq("my value") end it "unlinks the temporary file" do filename = Puppet::FileSystem::Uniquefile.open_tmp('foo') do |file| file.path end - expect(Puppet::FileSystem.exist?(filename)).to be_false + expect(Puppet::FileSystem.exist?(filename)).to be_falsey end it "unlinks the temporary file even if the block raises an error" do filename = nil begin Puppet::FileSystem::Uniquefile.open_tmp('foo') do |file| filename = file.path raise "error!" end rescue end - expect(Puppet::FileSystem.exist?(filename)).to be_false + expect(Puppet::FileSystem.exist?(filename)).to be_falsey end context "Ruby 1.9.3 Tempfile tests" do # the remaining tests in this file are ported directly from the ruby 1.9.3 source, # since most of this file was ported from there # see: https://github.com/ruby/ruby/blob/v1_9_3_547/test/test_tempfile.rb def tempfile(*args, &block) t = Puppet::FileSystem::Uniquefile.new(*args, &block) @tempfile = (t unless block) end after(:each) do if @tempfile @tempfile.close! end end it "creates tempfiles" do t = tempfile("foo") path = t.path t.write("hello world") t.close expect(File.read(path)).to eq("hello world") end it "saves in tmpdir by default" do t = tempfile("foo") expect(Dir.tmpdir).to eq(File.dirname(t.path)) end it "saves in given directory" do subdir = File.join(Dir.tmpdir, "tempfile-test-#{rand}") Dir.mkdir(subdir) begin tempfile = Tempfile.new("foo", subdir) tempfile.close begin expect(subdir).to eq(File.dirname(tempfile.path)) ensure tempfile.unlink end ensure Dir.rmdir(subdir) end end it "supports basename" do t = tempfile("foo") expect(File.basename(t.path)).to match(/^foo/) end it "supports basename with suffix" do t = tempfile(["foo", ".txt"]) expect(File.basename(t.path)).to match(/^foo/) expect(File.basename(t.path)).to match(/\.txt$/) end it "supports unlink" do t = tempfile("foo") path = t.path t.close expect(File.exist?(path)).to eq(true) t.unlink expect(File.exist?(path)).to eq(false) expect(t.path).to eq(nil) end it "supports closing" do t = tempfile("foo") expect(t.closed?).to eq(false) t.close expect(t.closed?).to eq(true) end it "supports closing and unlinking via boolean argument" do t = tempfile("foo") path = t.path t.close(true) expect(t.closed?).to eq(true) expect(t.path).to eq(nil) expect(File.exist?(path)).to eq(false) end context "on unix platforms", :unless => Puppet.features.microsoft_windows? do it "close doesn't unlink if already unlinked" do t = tempfile("foo") path = t.path t.unlink File.open(path, "w").close begin t.close(true) expect(File.exist?(path)).to eq(true) ensure File.unlink(path) rescue nil end end end it "supports close!" do t = tempfile("foo") path = t.path t.close! expect(t.closed?).to eq(true) expect(t.path).to eq(nil) expect(File.exist?(path)).to eq(false) end context "on unix platforms", :unless => Puppet.features.microsoft_windows? do it "close! doesn't unlink if already unlinked" do t = tempfile("foo") path = t.path t.unlink File.open(path, "w").close begin t.close! expect(File.exist?(path)).to eq(true) ensure File.unlink(path) rescue nil end end end it "close does not make path nil" do t = tempfile("foo") t.close expect(t.path.nil?).to eq(false) end it "close flushes buffer" do t = tempfile("foo") t.write("hello") t.close expect(File.size(t.path)).to eq(5) end end end diff --git a/spec/unit/file_system_spec.rb b/spec/unit/file_system_spec.rb index 90d943bbf..e6aa3268b 100644 --- a/spec/unit/file_system_spec.rb +++ b/spec/unit/file_system_spec.rb @@ -1,508 +1,508 @@ require 'spec_helper' require 'puppet/file_system' require 'puppet/util/platform' describe "Puppet::FileSystem" do include PuppetSpec::Files context "#exclusive_open" do it "opens ands allows updating of an existing file" do file = file_containing("file_to_update", "the contents") Puppet::FileSystem.exclusive_open(file, 0660, 'r+') do |fh| old = fh.read fh.truncate(0) fh.rewind fh.write("updated #{old}") end expect(Puppet::FileSystem.read(file)).to eq("updated the contents") end it "opens, creates ands allows updating of a new file" do file = tmpfile("file_to_update") Puppet::FileSystem.exclusive_open(file, 0660, 'w') do |fh| fh.write("updated new file") end expect(Puppet::FileSystem.read(file)).to eq("updated new file") end it "excludes other processes from updating at the same time", :unless => Puppet::Util::Platform.windows? do file = file_containing("file_to_update", "0") increment_counter_in_multiple_processes(file, 5, 'r+') expect(Puppet::FileSystem.read(file)).to eq("5") end it "excludes other processes from updating at the same time even when creating the file", :unless => Puppet::Util::Platform.windows? do file = tmpfile("file_to_update") increment_counter_in_multiple_processes(file, 5, 'a+') expect(Puppet::FileSystem.read(file)).to eq("5") end it "times out if the lock cannot be acquired in a specified amount of time", :unless => Puppet::Util::Platform.windows? do file = tmpfile("file_to_update") child = spawn_process_that_locks(file) expect do Puppet::FileSystem.exclusive_open(file, 0666, 'a', 0.1) do |f| end end.to raise_error(Timeout::Error) Process.kill(9, child) end def spawn_process_that_locks(file) read, write = IO.pipe child = Kernel.fork do read.close Puppet::FileSystem.exclusive_open(file, 0666, 'a') do |fh| write.write(true) write.close sleep 10 end end write.close read.read read.close child end def increment_counter_in_multiple_processes(file, num_procs, options) children = [] num_procs.times do children << Kernel.fork do Puppet::FileSystem.exclusive_open(file, 0660, options) do |fh| fh.rewind contents = (fh.read || 0).to_i fh.truncate(0) fh.rewind fh.write((contents + 1).to_s) end exit(0) end end children.each { |pid| Process.wait(pid) } end end describe "symlink", :if => ! Puppet.features.manages_symlinks? && Puppet.features.microsoft_windows? do let(:file) { tmpfile("somefile") } let(:missing_file) { tmpfile("missingfile") } let(:expected_msg) { "This version of Windows does not support symlinks. Windows Vista / 2008 or higher is required." } before :each do FileUtils.touch(file) end it "should raise an error when trying to create a symlink" do expect { Puppet::FileSystem.symlink(file, 'foo') }.to raise_error(Puppet::Util::Windows::Error) end it "should return false when trying to check if a path is a symlink" do - Puppet::FileSystem.symlink?(file).should be_false + Puppet::FileSystem.symlink?(file).should be_falsey end it "should raise an error when trying to read a symlink" do expect { Puppet::FileSystem.readlink(file) }.to raise_error(Puppet::Util::Windows::Error) end it "should return a File::Stat instance when calling stat on an existing file" do Puppet::FileSystem.stat(file).should be_instance_of(File::Stat) end it "should raise Errno::ENOENT when calling stat on a missing file" do expect { Puppet::FileSystem.stat(missing_file) }.to raise_error(Errno::ENOENT) end it "should fall back to stat when trying to lstat a file" do Puppet::Util::Windows::File.expects(:stat).with(Puppet::FileSystem.assert_path(file)) Puppet::FileSystem.lstat(file) end end describe "symlink", :if => Puppet.features.manages_symlinks? do let(:file) { tmpfile("somefile") } let(:missing_file) { tmpfile("missingfile") } let(:dir) { tmpdir("somedir") } before :each do FileUtils.touch(file) end it "should return true for exist? on a present file" do - Puppet::FileSystem.exist?(file).should be_true + Puppet::FileSystem.exist?(file).should be_truthy end it "should return true for file? on a present file" do - Puppet::FileSystem.file?(file).should be_true + Puppet::FileSystem.file?(file).should be_truthy end it "should return false for exist? on a non-existent file" do - Puppet::FileSystem.exist?(missing_file).should be_false + Puppet::FileSystem.exist?(missing_file).should be_falsey end it "should return true for exist? on a present directory" do - Puppet::FileSystem.exist?(dir).should be_true + Puppet::FileSystem.exist?(dir).should be_truthy end it "should return false for exist? on a dangling symlink" do symlink = tmpfile("somefile_link") Puppet::FileSystem.symlink(missing_file, symlink) - Puppet::FileSystem.exist?(missing_file).should be_false - Puppet::FileSystem.exist?(symlink).should be_false + Puppet::FileSystem.exist?(missing_file).should be_falsey + Puppet::FileSystem.exist?(symlink).should be_falsey end it "should return true for exist? on valid symlinks" do [file, dir].each do |target| symlink = tmpfile("#{Puppet::FileSystem.basename(target).to_s}_link") Puppet::FileSystem.symlink(target, symlink) - Puppet::FileSystem.exist?(target).should be_true - Puppet::FileSystem.exist?(symlink).should be_true + Puppet::FileSystem.exist?(target).should be_truthy + Puppet::FileSystem.exist?(symlink).should be_truthy end end it "should not create a symlink when the :noop option is specified" do [file, dir].each do |target| symlink = tmpfile("#{Puppet::FileSystem.basename(target)}_link") Puppet::FileSystem.symlink(target, symlink, { :noop => true }) - Puppet::FileSystem.exist?(target).should be_true - Puppet::FileSystem.exist?(symlink).should be_false + Puppet::FileSystem.exist?(target).should be_truthy + Puppet::FileSystem.exist?(symlink).should be_falsey end end it "should raise Errno::EEXIST if trying to create a file / directory symlink when the symlink path already exists as a file" do existing_file = tmpfile("#{Puppet::FileSystem.basename(file)}_link") FileUtils.touch(existing_file) [file, dir].each do |target| expect { Puppet::FileSystem.symlink(target, existing_file) }.to raise_error(Errno::EEXIST) - Puppet::FileSystem.exist?(existing_file).should be_true - Puppet::FileSystem.symlink?(existing_file).should be_false + Puppet::FileSystem.exist?(existing_file).should be_truthy + Puppet::FileSystem.symlink?(existing_file).should be_falsey end end it "should silently fail if trying to create a file / directory symlink when the symlink path already exists as a directory" do existing_dir = tmpdir("#{Puppet::FileSystem.basename(file)}_dir") [file, dir].each do |target| Puppet::FileSystem.symlink(target, existing_dir).should == 0 - Puppet::FileSystem.exist?(existing_dir).should be_true - File.directory?(existing_dir).should be_true - Puppet::FileSystem.symlink?(existing_dir).should be_false + Puppet::FileSystem.exist?(existing_dir).should be_truthy + File.directory?(existing_dir).should be_truthy + Puppet::FileSystem.symlink?(existing_dir).should be_falsey end end it "should silently fail to modify an existing directory symlink to reference a new file or directory" do [file, dir].each do |target| existing_dir = tmpdir("#{Puppet::FileSystem.basename(target)}_dir") symlink = tmpfile("#{Puppet::FileSystem.basename(existing_dir)}_link") Puppet::FileSystem.symlink(existing_dir, symlink) Puppet::FileSystem.readlink(symlink).should == Puppet::FileSystem.path_string(existing_dir) # now try to point it at the new target, no error raised, but file system unchanged Puppet::FileSystem.symlink(target, symlink).should == 0 Puppet::FileSystem.readlink(symlink).should == existing_dir.to_s end end it "should raise Errno::EEXIST if trying to modify a file symlink to reference a new file or directory" do symlink = tmpfile("#{Puppet::FileSystem.basename(file)}_link") file_2 = tmpfile("#{Puppet::FileSystem.basename(file)}_2") FileUtils.touch(file_2) # symlink -> file_2 Puppet::FileSystem.symlink(file_2, symlink) [file, dir].each do |target| expect { Puppet::FileSystem.symlink(target, symlink) }.to raise_error(Errno::EEXIST) Puppet::FileSystem.readlink(symlink).should == file_2.to_s end end it "should delete the existing file when creating a file / directory symlink with :force when the symlink path exists as a file" do [file, dir].each do |target| existing_file = tmpfile("#{Puppet::FileSystem.basename(target)}_existing") FileUtils.touch(existing_file) - Puppet::FileSystem.symlink?(existing_file).should be_false + Puppet::FileSystem.symlink?(existing_file).should be_falsey Puppet::FileSystem.symlink(target, existing_file, { :force => true }) - Puppet::FileSystem.symlink?(existing_file).should be_true + Puppet::FileSystem.symlink?(existing_file).should be_truthy Puppet::FileSystem.readlink(existing_file).should == target.to_s end end it "should modify an existing file symlink when using :force to reference a new file or directory" do [file, dir].each do |target| existing_file = tmpfile("#{Puppet::FileSystem.basename(target)}_existing") FileUtils.touch(existing_file) existing_symlink = tmpfile("#{Puppet::FileSystem.basename(existing_file)}_link") Puppet::FileSystem.symlink(existing_file, existing_symlink) Puppet::FileSystem.readlink(existing_symlink).should == existing_file.to_s Puppet::FileSystem.symlink(target, existing_symlink, { :force => true }) Puppet::FileSystem.readlink(existing_symlink).should == target.to_s end end it "should silently fail if trying to overwrite an existing directory with a new symlink when using :force to reference a file or directory" do [file, dir].each do |target| existing_dir = tmpdir("#{Puppet::FileSystem.basename(target)}_existing") Puppet::FileSystem.symlink(target, existing_dir, { :force => true }).should == 0 - Puppet::FileSystem.symlink?(existing_dir).should be_false + Puppet::FileSystem.symlink?(existing_dir).should be_falsey end end it "should silently fail if trying to modify an existing directory symlink when using :force to reference a new file or directory" do [file, dir].each do |target| existing_dir = tmpdir("#{Puppet::FileSystem.basename(target)}_existing") existing_symlink = tmpfile("#{Puppet::FileSystem.basename(existing_dir)}_link") Puppet::FileSystem.symlink(existing_dir, existing_symlink) Puppet::FileSystem.readlink(existing_symlink).should == existing_dir.to_s Puppet::FileSystem.symlink(target, existing_symlink, { :force => true }).should == 0 Puppet::FileSystem.readlink(existing_symlink).should == existing_dir.to_s end end it "should accept a string, Pathname or object with to_str (Puppet::Util::WatchedFile) for exist?" do [ tmpfile('bogus1'), Pathname.new(tmpfile('bogus2')), Puppet::Util::WatchedFile.new(tmpfile('bogus3')) - ].each { |f| Puppet::FileSystem.exist?(f).should be_false } + ].each { |f| Puppet::FileSystem.exist?(f).should be_falsey } end it "should return a File::Stat instance when calling stat on an existing file" do Puppet::FileSystem.stat(file).should be_instance_of(File::Stat) end it "should raise Errno::ENOENT when calling stat on a missing file" do expect { Puppet::FileSystem.stat(missing_file) }.to raise_error(Errno::ENOENT) end it "should be able to create a symlink, and verify it with symlink?" do symlink = tmpfile("somefile_link") Puppet::FileSystem.symlink(file, symlink) - Puppet::FileSystem.symlink?(symlink).should be_true + Puppet::FileSystem.symlink?(symlink).should be_truthy end it "should report symlink? as false on file, directory and missing files" do [file, dir, missing_file].each do |f| - Puppet::FileSystem.symlink?(f).should be_false + Puppet::FileSystem.symlink?(f).should be_falsey end end it "should return a File::Stat with ftype 'link' when calling lstat on a symlink pointing to existing file" do symlink = tmpfile("somefile_link") Puppet::FileSystem.symlink(file, symlink) stat = Puppet::FileSystem.lstat(symlink) stat.should be_instance_of(File::Stat) stat.ftype.should == 'link' end it "should return a File::Stat of ftype 'link' when calling lstat on a symlink pointing to missing file" do symlink = tmpfile("somefile_link") Puppet::FileSystem.symlink(missing_file, symlink) stat = Puppet::FileSystem.lstat(symlink) stat.should be_instance_of(File::Stat) stat.ftype.should == 'link' end it "should return a File::Stat of ftype 'file' when calling stat on a symlink pointing to existing file" do symlink = tmpfile("somefile_link") Puppet::FileSystem.symlink(file, symlink) stat = Puppet::FileSystem.stat(symlink) stat.should be_instance_of(File::Stat) stat.ftype.should == 'file' end it "should return a File::Stat of ftype 'directory' when calling stat on a symlink pointing to existing directory" do symlink = tmpfile("somefile_link") Puppet::FileSystem.symlink(dir, symlink) stat = Puppet::FileSystem.stat(symlink) stat.should be_instance_of(File::Stat) stat.ftype.should == 'directory' # on Windows, this won't get cleaned up if still linked Puppet::FileSystem.unlink(symlink) end it "should return a File::Stat of ftype 'file' when calling stat on a symlink pointing to another symlink" do # point symlink -> file symlink = tmpfile("somefile_link") Puppet::FileSystem.symlink(file, symlink) # point symlink2 -> symlink symlink2 = tmpfile("somefile_link2") Puppet::FileSystem.symlink(symlink, symlink2) Puppet::FileSystem.stat(symlink2).ftype.should == 'file' end it "should raise Errno::ENOENT when calling stat on a dangling symlink" do symlink = tmpfile("somefile_link") Puppet::FileSystem.symlink(missing_file, symlink) expect { Puppet::FileSystem.stat(symlink) }.to raise_error(Errno::ENOENT) end it "should be able to readlink to resolve the physical path to a symlink" do symlink = tmpfile("somefile_link") Puppet::FileSystem.symlink(file, symlink) - Puppet::FileSystem.exist?(file).should be_true + Puppet::FileSystem.exist?(file).should be_truthy Puppet::FileSystem.readlink(symlink).should == file.to_s end it "should not resolve entire symlink chain with readlink on a symlink'd symlink" do # point symlink -> file symlink = tmpfile("somefile_link") Puppet::FileSystem.symlink(file, symlink) # point symlink2 -> symlink symlink2 = tmpfile("somefile_link2") Puppet::FileSystem.symlink(symlink, symlink2) - Puppet::FileSystem.exist?(file).should be_true + Puppet::FileSystem.exist?(file).should be_truthy Puppet::FileSystem.readlink(symlink2).should == symlink.to_s end it "should be able to readlink to resolve the physical path to a dangling symlink" do symlink = tmpfile("somefile_link") Puppet::FileSystem.symlink(missing_file, symlink) - Puppet::FileSystem.exist?(missing_file).should be_false + Puppet::FileSystem.exist?(missing_file).should be_falsey Puppet::FileSystem.readlink(symlink).should == missing_file.to_s end it "should delete only the symlink and not the target when calling unlink instance method" do [file, dir].each do |target| symlink = tmpfile("#{Puppet::FileSystem.basename(target)}_link") Puppet::FileSystem.symlink(target, symlink) - Puppet::FileSystem.exist?(target).should be_true + Puppet::FileSystem.exist?(target).should be_truthy Puppet::FileSystem.readlink(symlink).should == target.to_s Puppet::FileSystem.unlink(symlink).should == 1 # count of files - Puppet::FileSystem.exist?(target).should be_true - Puppet::FileSystem.exist?(symlink).should be_false + Puppet::FileSystem.exist?(target).should be_truthy + Puppet::FileSystem.exist?(symlink).should be_falsey end end it "should delete only the symlink and not the target when calling unlink class method" do [file, dir].each do |target| symlink = tmpfile("#{Puppet::FileSystem.basename(target)}_link") Puppet::FileSystem.symlink(target, symlink) - Puppet::FileSystem.exist?(target).should be_true + Puppet::FileSystem.exist?(target).should be_truthy Puppet::FileSystem.readlink(symlink).should == target.to_s Puppet::FileSystem.unlink(symlink).should == 1 # count of files - Puppet::FileSystem.exist?(target).should be_true - Puppet::FileSystem.exist?(symlink).should be_false + Puppet::FileSystem.exist?(target).should be_truthy + Puppet::FileSystem.exist?(symlink).should be_falsey end end describe "unlink" do it "should delete files with unlink" do - Puppet::FileSystem.exist?(file).should be_true + Puppet::FileSystem.exist?(file).should be_truthy Puppet::FileSystem.unlink(file).should == 1 # count of files - Puppet::FileSystem.exist?(file).should be_false + Puppet::FileSystem.exist?(file).should be_falsey end it "should delete files with unlink class method" do - Puppet::FileSystem.exist?(file).should be_true + Puppet::FileSystem.exist?(file).should be_truthy Puppet::FileSystem.unlink(file).should == 1 # count of files - Puppet::FileSystem.exist?(file).should be_false + Puppet::FileSystem.exist?(file).should be_falsey end it "should delete multiple files with unlink class method" do paths = (1..3).collect do |i| f = tmpfile("somefile_#{i}") FileUtils.touch(f) - Puppet::FileSystem.exist?(f).should be_true + Puppet::FileSystem.exist?(f).should be_truthy f.to_s end Puppet::FileSystem.unlink(*paths).should == 3 # count of files - paths.each { |p| Puppet::FileSystem.exist?(p).should be_false } + paths.each { |p| Puppet::FileSystem.exist?(p).should be_falsey } end it "should raise Errno::EPERM or Errno::EISDIR when trying to delete a directory with the unlink class method" do - Puppet::FileSystem.exist?(dir).should be_true + Puppet::FileSystem.exist?(dir).should be_truthy ex = nil begin Puppet::FileSystem.unlink(dir) rescue Exception => e ex = e end [ Errno::EPERM, # Windows and OSX Errno::EISDIR # Linux ].should include(ex.class) - Puppet::FileSystem.exist?(dir).should be_true + Puppet::FileSystem.exist?(dir).should be_truthy end end describe "exclusive_create" do it "should create a file that doesn't exist" do - Puppet::FileSystem.exist?(missing_file).should be_false + Puppet::FileSystem.exist?(missing_file).should be_falsey Puppet::FileSystem.exclusive_create(missing_file, nil) {} - Puppet::FileSystem.exist?(missing_file).should be_true + Puppet::FileSystem.exist?(missing_file).should be_truthy end it "should raise Errno::EEXIST creating a file that does exist" do - Puppet::FileSystem.exist?(file).should be_true + Puppet::FileSystem.exist?(file).should be_truthy expect do Puppet::FileSystem.exclusive_create(file, nil) {} end.to raise_error(Errno::EEXIST) end end end end diff --git a/spec/unit/forge/module_release_spec.rb b/spec/unit/forge/module_release_spec.rb index ce00b27e9..7a588d7d6 100644 --- a/spec/unit/forge/module_release_spec.rb +++ b/spec/unit/forge/module_release_spec.rb @@ -1,218 +1,218 @@ # encoding: utf-8 require 'spec_helper' require 'puppet/forge' require 'net/http' require 'puppet/module_tool' describe Puppet::Forge::ModuleRelease do let(:agent) { "Test/1.0" } let(:repository) { Puppet::Forge::Repository.new('http://fake.com', agent) } let(:ssl_repository) { Puppet::Forge::Repository.new('https://fake.com', agent) } let(:api_version) { "v3" } let(:module_author) { "puppetlabs" } let(:module_name) { "stdlib" } let(:module_version) { "4.1.0" } let(:module_full_name) { "#{module_author}-#{module_name}" } let(:module_full_name_versioned) { "#{module_full_name}-#{module_version}" } let(:module_md5) { "bbf919d7ee9d278d2facf39c25578bf8" } let(:uri) { " "} let(:release) { Puppet::Forge::ModuleRelease.new(ssl_repository, JSON.parse(release_json)) } let(:mock_file) { mock_io = StringIO.new mock_io.stubs(:path).returns('/dev/null') mock_io } let(:mock_dir) { '/tmp' } shared_examples 'a module release' do def mock_digest_file_with_md5(md5) Digest::MD5.stubs(:file).returns(stub(:hexdigest => md5)) end describe '#prepare' do before :each do release.stubs(:tmpfile).returns(mock_file) release.stubs(:tmpdir).returns(mock_dir) end it 'should call sub methods with correct params' do release.expects(:download).with("/#{api_version}/files/#{module_full_name_versioned}.tar.gz", mock_file) release.expects(:validate_checksum).with(mock_file, module_md5) release.expects(:unpack).with(mock_file, mock_dir) release.prepare end end describe '#tmpfile' do it 'should be opened in binary mode' do Puppet::Forge::Cache.stubs(:base_path).returns(Dir.tmpdir) - release.send(:tmpfile).binmode?.should be_true + release.send(:tmpfile).binmode?.should be_truthy end end describe '#download' do it 'should call make_http_request with correct params' do # valid URI comes from file_uri in JSON blob above ssl_repository.expects(:make_http_request).with("/#{api_version}/files/#{module_full_name_versioned}.tar.gz", mock_file).returns(stub(:body => '{}', :code => '200')) release.send(:download, "/#{api_version}/files/#{module_full_name_versioned}.tar.gz", mock_file) end it 'should raise a response error when it receives an error from forge' do ssl_repository.stubs(:make_http_request).returns(stub(:body => '{"errors": ["error"]}', :code => '500', :message => 'server error')) expect { release.send(:download, "/some/path", mock_file)}. to raise_error Puppet::Forge::Errors::ResponseError end end describe '#verify_checksum' do it 'passes md5 check when valid' do # valid hash comes from file_md5 in JSON blob above mock_digest_file_with_md5(module_md5) release.send(:validate_checksum, mock_file, module_md5) end it 'fails md5 check when invalid' do mock_digest_file_with_md5('ffffffffffffffffffffffffffffffff') expect { release.send(:validate_checksum, mock_file, module_md5) }.to raise_error(RuntimeError, /did not match expected checksum/) end end describe '#unpack' do it 'should call unpacker with correct params' do Puppet::ModuleTool::Applications::Unpacker.expects(:unpack).with(mock_file.path, mock_dir).returns(true) release.send(:unpack, mock_file, mock_dir) end end end context 'standard forge module' do let(:release_json) do %Q{ { "uri": "/#{api_version}/releases/#{module_full_name_versioned}", "module": { "uri": "/#{api_version}/modules/#{module_full_name}", "name": "#{module_name}", "owner": { "uri": "/#{api_version}/users/#{module_author}", "username": "#{module_author}", "gravatar_id": "fdd009b7c1ec96e088b389f773e87aec" } }, "version": "#{module_version}", "metadata": { "types": [ ], "license": "Apache 2.0", "checksums": { }, "version": "#{module_version}", "description": "Standard Library for Puppet Modules", "source": "git://github.com/puppetlabs/puppetlabs-stdlib.git", "project_page": "https://github.com/puppetlabs/puppetlabs-stdlib", "summary": "Puppet Module Standard Library", "dependencies": [ ], "author": "#{module_author}", "name": "#{module_full_name}" }, "tags": [ "puppetlabs", "library", "stdlib", "standard", "stages" ], "file_uri": "/#{api_version}/files/#{module_full_name_versioned}.tar.gz", "file_size": 67586, "file_md5": "#{module_md5}", "downloads": 610751, "readme": "", "changelog": "", "license": "", "created_at": "2013-05-13 08:31:19 -0700", "updated_at": "2013-05-13 08:31:19 -0700", "deleted_at": null } } end it_behaves_like 'a module release' end context 'forge module with no dependencies field' do let(:release_json) do %Q{ { "uri": "/#{api_version}/releases/#{module_full_name_versioned}", "module": { "uri": "/#{api_version}/modules/#{module_full_name}", "name": "#{module_name}", "owner": { "uri": "/#{api_version}/users/#{module_author}", "username": "#{module_author}", "gravatar_id": "fdd009b7c1ec96e088b389f773e87aec" } }, "version": "#{module_version}", "metadata": { "types": [ ], "license": "Apache 2.0", "checksums": { }, "version": "#{module_version}", "description": "Standard Library for Puppet Modules", "source": "git://github.com/puppetlabs/puppetlabs-stdlib.git", "project_page": "https://github.com/puppetlabs/puppetlabs-stdlib", "summary": "Puppet Module Standard Library", "author": "#{module_author}", "name": "#{module_full_name}" }, "tags": [ "puppetlabs", "library", "stdlib", "standard", "stages" ], "file_uri": "/#{api_version}/files/#{module_full_name_versioned}.tar.gz", "file_size": 67586, "file_md5": "#{module_md5}", "downloads": 610751, "readme": "", "changelog": "", "license": "", "created_at": "2013-05-13 08:31:19 -0700", "updated_at": "2013-05-13 08:31:19 -0700", "deleted_at": null } } end it_behaves_like 'a module release' end context 'forge module with the minimal set of fields' do let(:release_json) do %Q{ { "uri": "/#{api_version}/releases/#{module_full_name_versioned}", "module": { "uri": "/#{api_version}/modules/#{module_full_name}", "name": "#{module_name}" }, "metadata": { "version": "#{module_version}", "name": "#{module_full_name}" }, "file_uri": "/#{api_version}/files/#{module_full_name_versioned}.tar.gz", "file_size": 67586, "file_md5": "#{module_md5}" } } end it_behaves_like 'a module release' end end diff --git a/spec/unit/functions4_spec.rb b/spec/unit/functions4_spec.rb index bdf012ffa..eaa42a1e9 100644 --- a/spec/unit/functions4_spec.rb +++ b/spec/unit/functions4_spec.rb @@ -1,660 +1,660 @@ require 'spec_helper' require 'puppet/pops' require 'puppet/loaders' require 'puppet_spec/pops' require 'puppet_spec/scope' module FunctionAPISpecModule class TestDuck end class TestFunctionLoader < Puppet::Pops::Loader::StaticLoader def initialize @functions = {} end def add_function(name, function) typed_name = Puppet::Pops::Loader::Loader::TypedName.new(:function, name) entry = Puppet::Pops::Loader::Loader::NamedEntry.new(typed_name, function, __FILE__) @functions[typed_name] = entry end # override StaticLoader def load_constant(typed_name) @functions[typed_name] end end end describe 'the 4x function api' do include FunctionAPISpecModule include PuppetSpec::Pops include PuppetSpec::Scope let(:loader) { FunctionAPISpecModule::TestFunctionLoader.new } it 'allows a simple function to be created without dispatch declaration' do f = Puppet::Functions.create_function('min') do def min(x,y) x <= y ? x : y end end # the produced result is a Class inheriting from Function expect(f.class).to be(Class) expect(f.superclass).to be(Puppet::Functions::Function) # and this class had the given name (not a real Ruby class name) expect(f.name).to eql('min') end it 'refuses to create functions that are not based on the Function class' do expect do Puppet::Functions.create_function('testing', Object) {} end.to raise_error(ArgumentError, 'Functions must be based on Puppet::Pops::Functions::Function. Got Object') end it 'refuses to create functions with parameters that are not named with a symbol' do expect do Puppet::Functions.create_function('testing') do dispatch :test do param 'Integer', 'not_symbol' end def test(x) end end end.to raise_error(ArgumentError, /Parameter name argument must be a Symbol/) end it 'a function without arguments can be defined and called without dispatch declaration' do f = create_noargs_function_class() func = f.new(:closure_scope, :loader) expect(func.call({})).to eql(10) end it 'an error is raised when calling a no arguments function with arguments' do f = create_noargs_function_class() func = f.new(:closure_scope, :loader) expect{func.call({}, 'surprise')}.to raise_error(ArgumentError, "function 'test' called with mis-matched arguments expected: test() - arg count {0} actual: test(String) - arg count {1}") end it 'a simple function can be called' do f = create_min_function_class() # TODO: Bogus parameters, not yet used func = f.new(:closure_scope, :loader) - expect(func.is_a?(Puppet::Functions::Function)).to be_true + expect(func.is_a?(Puppet::Functions::Function)).to be_truthy expect(func.call({}, 10,20)).to eql(10) end it 'an error is raised if called with too few arguments' do f = create_min_function_class() # TODO: Bogus parameters, not yet used func = f.new(:closure_scope, :loader) - expect(func.is_a?(Puppet::Functions::Function)).to be_true + expect(func.is_a?(Puppet::Functions::Function)).to be_truthy signature = 'Any x, Any y' expect do func.call({}, 10) end.to raise_error(ArgumentError, "function 'min' called with mis-matched arguments expected: min(#{signature}) - arg count {2} actual: min(Integer) - arg count {1}") end it 'an error is raised if called with too many arguments' do f = create_min_function_class() # TODO: Bogus parameters, not yet used func = f.new(:closure_scope, :loader) - expect(func.is_a?(Puppet::Functions::Function)).to be_true + expect(func.is_a?(Puppet::Functions::Function)).to be_truthy signature = 'Any x, Any y' expect do func.call({}, 10, 10, 10) end.to raise_error(ArgumentError, Regexp.new(Regexp.escape( "function 'min' called with mis-matched arguments expected: min(#{signature}) - arg count {2} actual: min(Integer, Integer, Integer) - arg count {3}"))) end it 'an error is raised if simple function-name and method are not matched' do expect do f = create_badly_named_method_function_class() end.to raise_error(ArgumentError, /Function Creation Error, cannot create a default dispatcher for function 'mix', no method with this name found/) end it 'the implementation separates dispatchers for different functions' do # this tests that meta programming / construction puts class attributes in the correct class f1 = create_min_function_class() f2 = create_max_function_class() d1 = f1.dispatcher d2 = f2.dispatcher expect(d1).to_not eql(d2) expect(d1.dispatchers[0]).to_not eql(d2.dispatchers[0]) end context 'when using regular dispatch' do it 'a function can be created using dispatch and called' do f = create_min_function_class_using_dispatch() func = f.new(:closure_scope, :loader) expect(func.call({}, 3,4)).to eql(3) end it 'an error is raised with reference to given parameter names when called with mis-matched arguments' do f = create_min_function_class_using_dispatch() # TODO: Bogus parameters, not yet used func = f.new(:closure_scope, :loader) - expect(func.is_a?(Puppet::Functions::Function)).to be_true + expect(func.is_a?(Puppet::Functions::Function)).to be_truthy expect do func.call({}, 10, 10, 10) end.to raise_error(ArgumentError, Regexp.new(Regexp.escape( "function 'min' called with mis-matched arguments expected: min(Numeric a, Numeric b) - arg count {2} actual: min(Integer, Integer, Integer) - arg count {3}"))) end it 'an error includes optional indicators and count for last element' do f = create_function_with_optionals_and_varargs() # TODO: Bogus parameters, not yet used func = f.new(:closure_scope, :loader) - expect(func.is_a?(Puppet::Functions::Function)).to be_true + expect(func.is_a?(Puppet::Functions::Function)).to be_truthy signature = 'Any x, Any y, Any a?, Any b?, Any c{0,}' expect do func.call({}, 10) end.to raise_error(ArgumentError, "function 'min' called with mis-matched arguments expected: min(#{signature}) - arg count {2,} actual: min(Integer) - arg count {1}") end it 'an error includes optional indicators and count for last element when defined via dispatch' do f = create_function_with_optionals_and_varargs_via_dispatch() # TODO: Bogus parameters, not yet used func = f.new(:closure_scope, :loader) - expect(func.is_a?(Puppet::Functions::Function)).to be_true + expect(func.is_a?(Puppet::Functions::Function)).to be_truthy expect do func.call({}, 10) end.to raise_error(ArgumentError, "function 'min' called with mis-matched arguments expected: min(Numeric x, Numeric y, Numeric a?, Numeric b?, Numeric c{0,}) - arg count {2,} actual: min(Integer) - arg count {1}") end it 'a function can be created using dispatch and called' do f = create_min_function_class_disptaching_to_two_methods() func = f.new(:closure_scope, :loader) expect(func.call({}, 3,4)).to eql(3) expect(func.call({}, 'Apple', 'Banana')).to eql('Apple') end it 'an error is raised with reference to multiple methods when called with mis-matched arguments' do f = create_min_function_class_disptaching_to_two_methods() # TODO: Bogus parameters, not yet used func = f.new(:closure_scope, :loader) - expect(func.is_a?(Puppet::Functions::Function)).to be_true + expect(func.is_a?(Puppet::Functions::Function)).to be_truthy expect do func.call({}, 10, 10, 10) end.to raise_error(ArgumentError, "function 'min' called with mis-matched arguments expected one of: min(Numeric a, Numeric b) - arg count {2} min(String s1, String s2) - arg count {2} actual: min(Integer, Integer, Integer) - arg count {3}") end context 'can use injection' do before :all do injector = Puppet::Pops::Binder::Injector.create('test') do bind.name('a_string').to('evoe') bind.name('an_int').to(42) end Puppet.push_context({:injector => injector}, "injector for testing function API") end after :all do Puppet.pop_context() end it 'attributes can be injected' do f1 = create_function_with_class_injection() f = f1.new(:closure_scope, :loader) expect(f.test_attr2()).to eql("evoe") expect(f.serial().produce(nil)).to eql(42) expect(f.test_attr().class.name).to eql("FunctionAPISpecModule::TestDuck") end it 'parameters can be injected and woven with regular dispatch' do f1 = create_function_with_param_injection_regular() f = f1.new(:closure_scope, :loader) expect(f.call(nil, 10, 20)).to eql("evoe! 10, and 20 < 42 = true") expect(f.call(nil, 50, 20)).to eql("evoe! 50, and 20 < 42 = false") end end context 'when requesting a type' do it 'responds with a Callable for a single signature' do tf = Puppet::Pops::Types::TypeFactory fc = create_min_function_class_using_dispatch() t = fc.dispatcher.to_type expect(t.class).to be(Puppet::Pops::Types::PCallableType) expect(t.param_types.class).to be(Puppet::Pops::Types::PTupleType) expect(t.param_types.types).to eql([tf.numeric(), tf.numeric()]) expect(t.block_type).to be_nil end it 'responds with a Variant[Callable...] for multiple signatures' do tf = Puppet::Pops::Types::TypeFactory fc = create_min_function_class_disptaching_to_two_methods() t = fc.dispatcher.to_type expect(t.class).to be(Puppet::Pops::Types::PVariantType) expect(t.types.size).to eql(2) t1 = t.types[0] expect(t1.param_types.class).to be(Puppet::Pops::Types::PTupleType) expect(t1.param_types.types).to eql([tf.numeric(), tf.numeric()]) expect(t1.block_type).to be_nil t2 = t.types[1] expect(t2.param_types.class).to be(Puppet::Pops::Types::PTupleType) expect(t2.param_types.types).to eql([tf.string(), tf.string()]) expect(t2.block_type).to be_nil end end context 'supports lambdas' do it 'such that, a required block can be defined and given as an argument' do # use a Function as callable the_callable = create_min_function_class().new(:closure_scope, :loader) the_function = create_function_with_required_block_all_defaults().new(:closure_scope, :loader) result = the_function.call({}, 10, the_callable) expect(result).to be(the_callable) end it 'such that, a missing required block when called raises an error' do # use a Function as callable the_function = create_function_with_required_block_all_defaults().new(:closure_scope, :loader) expect do the_function.call({}, 10) end.to raise_error(ArgumentError, "function 'test' called with mis-matched arguments expected: test(Integer x, Callable block) - arg count {2} actual: test(Integer) - arg count {1}") end it 'such that, an optional block can be defined and given as an argument' do # use a Function as callable the_callable = create_min_function_class().new(:closure_scope, :loader) the_function = create_function_with_optional_block_all_defaults().new(:closure_scope, :loader) result = the_function.call({}, 10, the_callable) expect(result).to be(the_callable) end it 'such that, an optional block can be omitted when called and gets the value nil' do # use a Function as callable the_function = create_function_with_optional_block_all_defaults().new(:closure_scope, :loader) expect(the_function.call({}, 10)).to be_nil end end context 'provides signature information' do it 'about capture rest (varargs)' do fc = create_function_with_optionals_and_varargs signatures = fc.signatures expect(signatures.size).to eql(1) signature = signatures[0] - expect(signature.last_captures_rest?).to be_true + expect(signature.last_captures_rest?).to be_truthy end it 'about optional and required parameters' do fc = create_function_with_optionals_and_varargs signature = fc.signatures[0] expect(signature.args_range).to eql( [2, Float::INFINITY ] ) - expect(signature.infinity?(signature.args_range[1])).to be_true + expect(signature.infinity?(signature.args_range[1])).to be_truthy end it 'about block not being allowed' do fc = create_function_with_optionals_and_varargs signature = fc.signatures[0] expect(signature.block_range).to eql( [ 0, 0 ] ) expect(signature.block_type).to be_nil end it 'about required block' do fc = create_function_with_required_block_all_defaults signature = fc.signatures[0] expect(signature.block_range).to eql( [ 1, 1 ] ) expect(signature.block_type).to_not be_nil end it 'about optional block' do fc = create_function_with_optional_block_all_defaults signature = fc.signatures[0] expect(signature.block_range).to eql( [ 0, 1 ] ) expect(signature.block_type).to_not be_nil end it 'about the type' do fc = create_function_with_optional_block_all_defaults signature = fc.signatures[0] expect(signature.type.class).to be(Puppet::Pops::Types::PCallableType) end it 'about parameter names obtained from ruby introspection' do fc = create_min_function_class signature = fc.signatures[0] expect(signature.parameter_names).to eql(['x', 'y']) end it 'about parameter names specified with dispatch' do fc = create_min_function_class_using_dispatch signature = fc.signatures[0] expect(signature.parameter_names).to eql([:a, :b]) end it 'about block_name when it is *not* given in the definition' do # neither type, nor name fc = create_function_with_required_block_all_defaults signature = fc.signatures[0] expect(signature.block_name).to eql(:block) # no name given, only type fc = create_function_with_required_block_given_type signature = fc.signatures[0] expect(signature.block_name).to eql(:block) end it 'about block_name when it *is* given in the definition' do # neither type, nor name fc = create_function_with_required_block_default_type signature = fc.signatures[0] expect(signature.block_name).to eql(:the_block) # no name given, only type fc = create_function_with_required_block_fully_specified signature = fc.signatures[0] expect(signature.block_name).to eql(:the_block) end end context 'supports calling other functions' do before(:all) do Puppet.push_context( {:loaders => Puppet::Pops::Loaders.new(Puppet::Node::Environment.create(:testing, []))}) end after(:all) do Puppet.pop_context() end it 'such that, other functions are callable by name' do fc = Puppet::Functions.create_function('test') do def test() # Call a function available in the puppet system call_function('assert_type', 'Integer', 10) end end # initiate the function the same way the loader initiates it f = fc.new(:closure_scope, Puppet.lookup(:loaders).puppet_system_loader) expect(f.call({})).to eql(10) end it 'such that, calling a non existing function raises an error' do fc = Puppet::Functions.create_function('test') do def test() # Call a function not available in the puppet system call_function('no_such_function', 'Integer', 'hello') end end # initiate the function the same way the loader initiates it f = fc.new(:closure_scope, Puppet.lookup(:loaders).puppet_system_loader) expect{f.call({})}.to raise_error(ArgumentError, "Function test(): cannot call function 'no_such_function' - not found") end end context 'supports calling ruby functions with lambda from puppet' do before(:all) do Puppet.push_context( {:loaders => Puppet::Pops::Loaders.new(Puppet::Node::Environment.create(:testing, []))}) end after(:all) do Puppet.pop_context() end before(:each) do Puppet[:strict_variables] = true end let(:parser) { Puppet::Pops::Parser::EvaluatingParser.new } let(:node) { 'node.example.com' } let(:scope) { s = create_test_scope_for_node(node); s } it 'function with required block can be called' do # construct ruby function to call fc = Puppet::Functions.create_function('testing::test') do dispatch :test do param 'Integer', :x # block called 'the_block', and using "all_callables" required_block_param #(all_callables(), 'the_block') end def test(x, block) # call the block with x block.call(x) end end # add the function to the loader (as if it had been loaded from somewhere) the_loader = loader() f = fc.new({}, the_loader) loader.add_function('testing::test', f) # evaluate a puppet call source = "testing::test(10) |$x| { $x+1 }" program = parser.parse_string(source, __FILE__) Puppet::Pops::Adapters::LoaderAdapter.adapt(program.model).loader = the_loader expect(parser.evaluate(scope, program)).to eql(11) end end end def create_noargs_function_class f = Puppet::Functions.create_function('test') do def test() 10 end end end def create_min_function_class f = Puppet::Functions.create_function('min') do def min(x,y) x <= y ? x : y end end end def create_max_function_class f = Puppet::Functions.create_function('max') do def max(x,y) x >= y ? x : y end end end def create_badly_named_method_function_class f = Puppet::Functions.create_function('mix') do def mix_up(x,y) x <= y ? x : y end end end def create_min_function_class_using_dispatch f = Puppet::Functions.create_function('min') do dispatch :min do param 'Numeric', :a param 'Numeric', :b end def min(x,y) x <= y ? x : y end end end def create_min_function_class_disptaching_to_two_methods f = Puppet::Functions.create_function('min') do dispatch :min do param 'Numeric', :a param 'Numeric', :b end dispatch :min_s do param 'String', :s1 param 'String', :s2 end def min(x,y) x <= y ? x : y end def min_s(x,y) cmp = (x.downcase <=> y.downcase) cmp <= 0 ? x : y end end end def create_function_with_optionals_and_varargs f = Puppet::Functions.create_function('min') do def min(x,y,a=1, b=1, *c) x <= y ? x : y end end end def create_function_with_optionals_and_varargs_via_dispatch f = Puppet::Functions.create_function('min') do dispatch :min do param 'Numeric', :x param 'Numeric', :y param 'Numeric', :a param 'Numeric', :b param 'Numeric', :c arg_count 2, :default end def min(x,y,a=1, b=1, *c) x <= y ? x : y end end end def create_function_with_class_injection f = Puppet::Functions.create_function('test', Puppet::Functions::InternalFunction) do attr_injected Puppet::Pops::Types::TypeFactory.type_of(FunctionAPISpecModule::TestDuck), :test_attr attr_injected Puppet::Pops::Types::TypeFactory.string(), :test_attr2, "a_string" attr_injected_producer Puppet::Pops::Types::TypeFactory.integer(), :serial, "an_int" def test(x,y,a=1, b=1, *c) x <= y ? x : y end end end def create_function_with_param_injection_regular f = Puppet::Functions.create_function('test', Puppet::Functions::InternalFunction) do attr_injected Puppet::Pops::Types::TypeFactory.type_of(FunctionAPISpecModule::TestDuck), :test_attr attr_injected Puppet::Pops::Types::TypeFactory.string(), :test_attr2, "a_string" attr_injected_producer Puppet::Pops::Types::TypeFactory.integer(), :serial, "an_int" dispatch :test do injected_param Puppet::Pops::Types::TypeFactory.string, :x, 'a_string' injected_producer_param Puppet::Pops::Types::TypeFactory.integer, :y, 'an_int' param 'Scalar', :a param 'Scalar', :b end def test(x,y,a,b) y_produced = y.produce(nil) "#{x}! #{a}, and #{b} < #{y_produced} = #{ !!(a < y_produced && b < y_produced)}" end end end def create_function_with_required_block_all_defaults f = Puppet::Functions.create_function('test') do dispatch :test do param 'Integer', :x # use defaults, any callable, name is 'block' required_block_param end def test(x, block) # returns the block to make it easy to test what it got when called block end end end def create_function_with_required_block_default_type f = Puppet::Functions.create_function('test') do dispatch :test do param 'Integer', :x # use defaults, any callable, name is 'block' required_block_param :the_block end def test(x, block) # returns the block to make it easy to test what it got when called block end end end def create_function_with_required_block_given_type f = Puppet::Functions.create_function('test') do dispatch :test do param 'Integer', :x required_block_param end def test(x, block) # returns the block to make it easy to test what it got when called block end end end def create_function_with_required_block_fully_specified f = Puppet::Functions.create_function('test') do dispatch :test do param 'Integer', :x # use defaults, any callable, name is 'block' required_block_param('Callable', :the_block) end def test(x, block) # returns the block to make it easy to test what it got when called block end end end def create_function_with_optional_block_all_defaults f = Puppet::Functions.create_function('test') do dispatch :test do param 'Integer', :x # use defaults, any callable, name is 'block' optional_block_param end def test(x, block=nil) # returns the block to make it easy to test what it got when called # a default of nil must be used or the call will fail with a missing parameter block end end end end diff --git a/spec/unit/graph/simple_graph_spec.rb b/spec/unit/graph/simple_graph_spec.rb index a6ae4d9c4..fbec3cb93 100755 --- a/spec/unit/graph/simple_graph_spec.rb +++ b/spec/unit/graph/simple_graph_spec.rb @@ -1,717 +1,717 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/graph' describe Puppet::Graph::SimpleGraph do it "should return the number of its vertices as its length" do @graph = Puppet::Graph::SimpleGraph.new @graph.add_vertex("one") @graph.add_vertex("two") @graph.size.should == 2 end it "should consider itself a directed graph" do - Puppet::Graph::SimpleGraph.new.directed?.should be_true + Puppet::Graph::SimpleGraph.new.directed?.should be_truthy end it "should provide a method for reversing the graph" do @graph = Puppet::Graph::SimpleGraph.new @graph.add_edge(:one, :two) - @graph.reversal.edge?(:two, :one).should be_true + @graph.reversal.edge?(:two, :one).should be_truthy end it "should be able to produce a dot graph" do @graph = Puppet::Graph::SimpleGraph.new class FauxVertex def ref "never mind" end end v1 = FauxVertex.new v2 = FauxVertex.new @graph.add_edge(v1, v2) expect { @graph.to_dot_graph }.to_not raise_error end describe "when managing vertices" do before do @graph = Puppet::Graph::SimpleGraph.new end it "should provide a method to add a vertex" do @graph.add_vertex(:test) - @graph.vertex?(:test).should be_true + @graph.vertex?(:test).should be_truthy 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) 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 + @graph.vertex?(:test).should be_truthy end it "should return false when asked if a non-vertex is present" do - @graph.vertex?(:test).should be_false + @graph.vertex?(:test).should be_falsey 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 + @graph.vertex?(:one).should be_falsey end it "should do nothing when a non-vertex is asked to be removed" do expect { @graph.remove_vertex!(:one) }.to_not raise_error end end describe "when managing edges" do before do @graph = Puppet::Graph::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 + @graph.edge?(:one, :two).should be_truthy 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 + @graph.edge?(:one, :two).should be_truthy 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 + @graph.edge?(:one, :two).should be_truthy 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 + @graph.vertex?(:one).should be_truthy 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 + @graph.vertex?(:two).should be_truthy 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 + @graph.edge?(edge.source, edge.target).should be_falsey 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.edge?(:one, :two).should be_falsey + @graph.edge?(:two, :three).should be_falsey @graph.edges.length.should == 0 end end describe "when finding adjacent vertices" do before do @graph = Puppet::Graph::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::Graph::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::Graph::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::Graph::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 + @graph.reversal.edge?(:two, :one).should be_truthy 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 + @graph.vertex?(:one).should be_truthy + @graph.vertex?(:two).should be_truthy 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::Graph::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 |cycle| cycle.map do |resource| resource.name end end end it "should fail on two-vertex loops" do add_edges :a => :b, :b => :a 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 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 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 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" simplify(@graph.find_cycles_in_graph).should be == [["a", "b"]] end it "cycle discovery handles a self-loop cycle" do add_edges :a => :a simplify(@graph.find_cycles_in_graph).should be == [["a"]] end it "cycle discovery should handle two distinct cycles" do add_edges "a" => "a1", "a1" => "a" add_edges "b" => "b1", "b1" => "b" simplify(@graph.find_cycles_in_graph).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" simplify(@graph.find_cycles_in_graph).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" simplify(@graph.find_cycles_in_graph).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 simplify(@graph.find_cycles_in_graph).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::Graph::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 end describe Puppet::Graph::SimpleGraph do before do @graph = Puppet::Graph::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" do before do @graph = Puppet::Graph::SimpleGraph.new # Resource is a String here although not for realz. Stub [] to always return nil # because indexing a String with a non-Integer throws an exception (and none of # these tests need anything meaningful from []). resource = "a" resource.stubs(:[]) @event = Puppet::Transaction::Event.new(:name => :yay, :resource => resource) @none = Puppet::Transaction::Event.new(:name => :NONE, :resource => resource) @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::Graph::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 it "should serialize to YAML using the old format by default" do Puppet::Graph::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::Graph::SimpleGraph.use_new_yaml_format Puppet::Graph::SimpleGraph.use_new_yaml_format = (which_format == :new) YAML.dump(graph) ensure Puppet::Graph::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::Graph::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.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['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| %w{source target}.each { |x| edge.keys.should include(x) } edge.keys.each { |x| ['source', 'target', 'callback', 'event'].should include(x) } %w{source target callback event}.collect { |x| edge[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['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.keys.sort.should == %w{adjacencies vertex} value['vertex'].should equal(key) adjacencies = 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['source'] == desired_source && edge['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::Graph::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::Graph::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::Graph::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/indirector/file_bucket_file/file_spec.rb b/spec/unit/indirector/file_bucket_file/file_spec.rb index 56dacb7d7..13c327ee2 100755 --- a/spec/unit/indirector/file_bucket_file/file_spec.rb +++ b/spec/unit/indirector/file_bucket_file/file_spec.rb @@ -1,286 +1,286 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector/file_bucket_file/file' require 'puppet/util/platform' describe Puppet::FileBucketFile::File, :uses_checksums => true do include PuppetSpec::Files describe "non-stubbing tests" do include PuppetSpec::Files def save_bucket_file(contents, path = "/who_cares") bucket_file = Puppet::FileBucket::File.new(contents) Puppet::FileBucket::File.indirection.save(bucket_file, "#{bucket_file.name}#{path}") bucket_file.checksum_data end describe "when servicing a save request" do it "should return a result whose content is empty" do bucket_file = Puppet::FileBucket::File.new('stuff') result = Puppet::FileBucket::File.indirection.save(bucket_file, "md5/c13d88cb4cb02003daedb8a84e5d272a") result.contents.should be_empty end it "deals with multiple processes saving at the same time", :unless => Puppet::Util::Platform.windows? do bucket_file = Puppet::FileBucket::File.new("contents") children = [] 5.times do |count| children << Kernel.fork do save_bucket_file("contents", "/testing") exit(0) end end children.each { |child| Process.wait(child) } paths = File.read("#{Puppet[:bucketdir]}/9/8/b/f/7/d/8/c/98bf7d8c15784f0a3d63204441e1e2aa/paths").lines.to_a paths.length.should == 1 - Puppet::FileBucket::File.indirection.head("#{bucket_file.checksum_type}/#{bucket_file.checksum_data}/testing").should be_true + Puppet::FileBucket::File.indirection.head("#{bucket_file.checksum_type}/#{bucket_file.checksum_data}/testing").should be_truthy end it "fails if the contents collide with existing contents" do # This is the shortest known MD5 collision. See http://eprint.iacr.org/2010/643.pdf first_contents = [0x6165300e,0x87a79a55,0xf7c60bd0,0x34febd0b, 0x6503cf04,0x854f709e,0xfb0fc034,0x874c9c65, 0x2f94cc40,0x15a12deb,0x5c15f4a3,0x490786bb, 0x6d658673,0xa4341f7d,0x8fd75920,0xefd18d5a].pack("I" * 16) collision_contents = [0x6165300e,0x87a79a55,0xf7c60bd0,0x34febd0b, 0x6503cf04,0x854f749e,0xfb0fc034,0x874c9c65, 0x2f94cc40,0x15a12deb,0xdc15f4a3,0x490786bb, 0x6d658673,0xa4341f7d,0x8fd75920,0xefd18d5a].pack("I" * 16) save_bucket_file(first_contents, "/foo/bar") expect do save_bucket_file(collision_contents, "/foo/bar") end.to raise_error(Puppet::FileBucket::BucketError, /Got passed new contents/) end describe "when supplying a path" do with_digest_algorithms do it "should store the path if not already stored" do checksum = save_bucket_file(plaintext, "/foo/bar") dir_path = "#{Puppet[:bucketdir]}/#{bucket_dir}" contents_file = "#{dir_path}/contents" paths_file = "#{dir_path}/paths" Puppet::FileSystem.binread(contents_file).should == plaintext Puppet::FileSystem.read(paths_file).should == "foo/bar\n" end it "should leave the paths file alone if the path is already stored" do checksum = save_bucket_file(plaintext, "/foo/bar") checksum = save_bucket_file(plaintext, "/foo/bar") dir_path = "#{Puppet[:bucketdir]}/#{bucket_dir}" Puppet::FileSystem.binread("#{dir_path}/contents").should == plaintext File.read("#{dir_path}/paths").should == "foo/bar\n" end it "should store an additional path if the new path differs from those already stored" do checksum = save_bucket_file(plaintext, "/foo/bar") checksum = save_bucket_file(plaintext, "/foo/baz") dir_path = "#{Puppet[:bucketdir]}/#{bucket_dir}" Puppet::FileSystem.binread("#{dir_path}/contents").should == plaintext File.read("#{dir_path}/paths").should == "foo/bar\nfoo/baz\n" end end end describe "when not supplying a path" do with_digest_algorithms do it "should save the file and create an empty paths file" do checksum = save_bucket_file(plaintext, "") dir_path = "#{Puppet[:bucketdir]}/#{bucket_dir}" Puppet::FileSystem.binread("#{dir_path}/contents").should == plaintext File.read("#{dir_path}/paths").should == "" end end end end describe "when servicing a head/find request" do with_digest_algorithms do let(:not_bucketed_plaintext) { "other stuff" } let(:not_bucketed_checksum) { digest(not_bucketed_plaintext) } describe "when supplying a path" do it "should return false/nil if the file isn't bucketed" do Puppet::FileBucket::File.indirection.head("#{digest_algorithm}/#{not_bucketed_checksum}/foo/bar").should == false Puppet::FileBucket::File.indirection.find("#{digest_algorithm}/#{not_bucketed_checksum}/foo/bar").should == nil end it "should return false/nil if the file is bucketed but with a different path" do checksum = save_bucket_file("I'm the contents of a file", '/foo/bar') Puppet::FileBucket::File.indirection.head("#{digest_algorithm}/#{checksum}/foo/baz").should == false Puppet::FileBucket::File.indirection.find("#{digest_algorithm}/#{checksum}/foo/baz").should == nil end it "should return true/file if the file is already bucketed with the given path" do contents = "I'm the contents of a file" checksum = save_bucket_file(contents, '/foo/bar') Puppet::FileBucket::File.indirection.head("#{digest_algorithm}/#{checksum}/foo/bar").should == true find_result = Puppet::FileBucket::File.indirection.find("#{digest_algorithm}/#{checksum}/foo/bar") find_result.checksum.should == "{#{digest_algorithm}}#{checksum}" find_result.to_s.should == contents end end describe "when not supplying a path" do [false, true].each do |trailing_slash| describe "#{trailing_slash ? 'with' : 'without'} a trailing slash" do trailing_string = trailing_slash ? '/' : '' it "should return false/nil if the file isn't bucketed" do Puppet::FileBucket::File.indirection.head("#{digest_algorithm}/#{not_bucketed_checksum}#{trailing_string}").should == false Puppet::FileBucket::File.indirection.find("#{digest_algorithm}/#{not_bucketed_checksum}#{trailing_string}").should == nil end it "should return true/file if the file is already bucketed" do # this one replaces most of the lets in the "when # digest_digest_algorithm is set..." shared context, but it still needs digest_algorithm contents = "I'm the contents of a file" checksum = save_bucket_file(contents, '/foo/bar') Puppet::FileBucket::File.indirection.head("#{digest_algorithm}/#{checksum}#{trailing_string}").should == true find_result = Puppet::FileBucket::File.indirection.find("#{digest_algorithm}/#{checksum}#{trailing_string}") find_result.checksum.should == "{#{digest_algorithm}}#{checksum}" find_result.to_s.should == contents end end end end end end describe "when diffing files", :unless => Puppet.features.microsoft_windows? do with_digest_algorithms do let(:not_bucketed_plaintext) { "other stuff" } let(:not_bucketed_checksum) { digest(not_bucketed_plaintext) } it "should generate an empty string if there is no diff" do checksum = save_bucket_file("I'm the contents of a file") Puppet::FileBucket::File.indirection.find("#{digest_algorithm}/#{checksum}", :diff_with => checksum).should == '' end it "should generate a proper diff if there is a diff" do checksum1 = save_bucket_file("foo\nbar\nbaz") checksum2 = save_bucket_file("foo\nbiz\nbaz") diff = Puppet::FileBucket::File.indirection.find("#{digest_algorithm}/#{checksum1}", :diff_with => checksum2) diff.should == "2c2\n< bar\n---\n> biz\n" end it "should raise an exception if the hash to diff against isn't found" do checksum = save_bucket_file("whatever") expect do Puppet::FileBucket::File.indirection.find("#{digest_algorithm}/#{checksum}", :diff_with => not_bucketed_checksum) end.to raise_error "could not find diff_with #{not_bucketed_checksum}" end it "should return nil if the hash to diff from isn't found" do checksum = save_bucket_file("whatever") Puppet::FileBucket::File.indirection.find("#{digest_algorithm}/#{not_bucketed_checksum}", :diff_with => checksum).should == nil end end end end [true, false].each do |override_bucket_path| describe "when bucket path #{override_bucket_path ? 'is' : 'is not'} overridden" do [true, false].each do |supply_path| describe "when #{supply_path ? 'supplying' : 'not supplying'} a path" do with_digest_algorithms do before :each do Puppet.settings.stubs(:use) @store = Puppet::FileBucketFile::File.new @bucket_top_dir = tmpdir("bucket") if override_bucket_path Puppet[:bucketdir] = "/bogus/path" # should not be used else Puppet[:bucketdir] = @bucket_top_dir end @dir = "#{@bucket_top_dir}/#{bucket_dir}" @contents_path = "#{@dir}/contents" end describe "when retrieving files" do before :each do request_options = {} if override_bucket_path request_options[:bucket_path] = @bucket_top_dir end key = "#{digest_algorithm}/#{checksum}" if supply_path key += "/path/to/file" end @request = Puppet::Indirector::Request.new(:indirection_name, :find, key, nil, request_options) end def make_bucketed_file FileUtils.mkdir_p(@dir) File.open(@contents_path, 'wb') { |f| f.write plaintext } end it "should return an instance of Puppet::FileBucket::File created with the content if the file exists" do make_bucketed_file if supply_path @store.find(@request).should == nil @store.head(@request).should == false # because path didn't match else bucketfile = @store.find(@request) bucketfile.should be_a(Puppet::FileBucket::File) bucketfile.contents.should == plaintext @store.head(@request).should == true end end it "should return nil if no file is found" do @store.find(@request).should be_nil @store.head(@request).should == false end end describe "when saving files" do it "should save the contents to the calculated path" do options = {} if override_bucket_path options[:bucket_path] = @bucket_top_dir end key = "#{digest_algorithm}/#{checksum}" if supply_path key += "//path/to/file" end file_instance = Puppet::FileBucket::File.new(plaintext, options) request = Puppet::Indirector::Request.new(:indirection_name, :save, key, file_instance) @store.save(request) Puppet::FileSystem.binread("#{@dir}/contents").should == plaintext end end end end end end end end diff --git a/spec/unit/indirector/json_spec.rb b/spec/unit/indirector/json_spec.rb index 656d156c0..acf8f2764 100755 --- a/spec/unit/indirector/json_spec.rb +++ b/spec/unit/indirector/json_spec.rb @@ -1,192 +1,192 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/files' require 'puppet/indirector/indirector_testing/json' describe Puppet::Indirector::JSON do include PuppetSpec::Files subject { Puppet::IndirectorTesting::JSON.new } let :model do Puppet::IndirectorTesting end let :indirection do model.indirection end context "#path" do before :each do Puppet[:server_datadir] = '/sample/datadir/master' Puppet[:client_datadir] = '/sample/datadir/client' end it "uses the :server_datadir setting if this is the master" do Puppet.run_mode.stubs(:master?).returns(true) expected = File.join(Puppet[:server_datadir], 'indirector_testing', 'testing.json') subject.path('testing').should == expected end it "uses the :client_datadir setting if this is not the master" do Puppet.run_mode.stubs(:master?).returns(false) expected = File.join(Puppet[:client_datadir], 'indirector_testing', 'testing.json') subject.path('testing').should == expected end it "overrides the default extension with a supplied value" do Puppet.run_mode.stubs(:master?).returns(true) expected = File.join(Puppet[:server_datadir], 'indirector_testing', 'testing.not-json') subject.path('testing', '.not-json').should == expected end ['../foo', '..\\foo', './../foo', '.\\..\\foo', '/foo', '//foo', '\\foo', '\\\\goo', "test\0/../bar", "test\0\\..\\bar", "..\\/bar", "/tmp/bar", "/tmp\\bar", "tmp\\bar", " / bar", " /../ bar", " \\..\\ bar", "c:\\foo", "c:/foo", "\\\\?\\UNC\\bar", "\\\\foo\\bar", "\\\\?\\c:\\foo", "//?/UNC/bar", "//foo/bar", "//?/c:/foo", ].each do |input| it "should resist directory traversal attacks (#{input.inspect})" do expect { subject.path(input) }.to raise_error ArgumentError, 'invalid key' end end end context "handling requests" do before :each do Puppet.run_mode.stubs(:master?).returns(true) Puppet[:server_datadir] = tmpdir('jsondir') FileUtils.mkdir_p(File.join(Puppet[:server_datadir], 'indirector_testing')) end let :file do subject.path(request.key) end def with_content(text) FileUtils.mkdir_p(File.dirname(file)) File.open(file, 'w') {|f| f.puts text } yield if block_given? end it "data saves and then loads again correctly" do subject.save(indirection.request(:save, 'example', model.new('banana'))) subject.find(indirection.request(:find, 'example', nil)).value.should == 'banana' end context "#find" do let :request do indirection.request(:find, 'example', nil) end it "returns nil if the file doesn't exist" do subject.find(request).should be_nil end it "raises a descriptive error when the file can't be read" do with_content(model.new('foo').to_pson) do # I don't like this, but there isn't a credible alternative that # also works on Windows, so a stub it is. At least the expectation # will fail if the implementation changes. Sorry to the next dev. File.expects(:read).with(file).raises(Errno::EPERM) expect { subject.find(request) }. to raise_error Puppet::Error, /Could not read JSON/ end end it "raises a descriptive error when the file content is invalid" do with_content("this is totally invalid JSON") do expect { subject.find(request) }. to raise_error Puppet::Error, /Could not parse JSON data/ end end it "should return an instance of the indirected object when valid" do with_content(model.new(1).to_pson) do instance = subject.find(request) instance.should be_an_instance_of model instance.value.should == 1 end end end context "#save" do let :instance do model.new(4) end let :request do indirection.request(:find, 'example', instance) end it "should save the instance of the request as JSON to disk" do subject.save(request) content = File.read(file) content.should =~ /"value"\s*:\s*4/ end it "should create the indirection directory if required" do target = File.join(Puppet[:server_datadir], 'indirector_testing') Dir.rmdir(target) subject.save(request) File.should be_directory(target) end end context "#destroy" do let :request do indirection.request(:find, 'example', nil) end it "removes an existing file" do with_content('hello') do subject.destroy(request) end - Puppet::FileSystem.exist?(file).should be_false + Puppet::FileSystem.exist?(file).should be_falsey end it "silently succeeds when files don't exist" do Puppet::FileSystem.unlink(file) rescue nil - subject.destroy(request).should be_true + subject.destroy(request).should be_truthy end it "raises an informative error for other failures" do Puppet::FileSystem.stubs(:unlink).with(file).raises(Errno::EPERM, 'fake permission problem') with_content('hello') do expect { subject.destroy(request) }.to raise_error(Puppet::Error) end Puppet::FileSystem.unstub(:unlink) # thanks, mocha end end end context "#search" do before :each do Puppet.run_mode.stubs(:master?).returns(true) Puppet[:server_datadir] = tmpdir('jsondir') FileUtils.mkdir_p(File.join(Puppet[:server_datadir], 'indirector_testing')) end def request(glob) indirection.request(:search, glob, nil) end def create_file(name, value = 12) File.open(subject.path(name, ''), 'w') do |f| f.puts Puppet::IndirectorTesting.new(value).to_pson end end it "returns an empty array when nothing matches the key as a glob" do subject.search(request('*')).should == [] end it "returns an array with one item if one item matches" do create_file('foo.json', 'foo') create_file('bar.json', 'bar') subject.search(request('f*')).map(&:value).should == ['foo'] end it "returns an array of items when more than one item matches" do create_file('foo.json', 'foo') create_file('bar.json', 'bar') create_file('baz.json', 'baz') subject.search(request('b*')).map(&:value).should =~ ['bar', 'baz'] end it "only items with the .json extension" do create_file('foo.json', 'foo-json') create_file('foo.pson', 'foo-pson') create_file('foo.json~', 'foo-backup') subject.search(request('f*')).map(&:value).should == ['foo-json'] end end end diff --git a/spec/unit/indirector/ldap_spec.rb b/spec/unit/indirector/ldap_spec.rb index eb8be0f04..aecae903b 100755 --- a/spec/unit/indirector/ldap_spec.rb +++ b/spec/unit/indirector/ldap_spec.rb @@ -1,137 +1,137 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector/ldap' describe Puppet::Indirector::Ldap do before do @indirection = stub 'indirection', :name => :testing Puppet::Indirector::Indirection.stubs(:instance).returns(@indirection) module Testing; end @ldap_class = class Testing::MyLdap < Puppet::Indirector::Ldap self end @connection = mock 'ldap' @searcher = @ldap_class.new end describe "when searching ldap" do before do # Stub everything, and we can selectively replace with an expect as # we need to for testing. @searcher.stubs(:connection).returns(@connection) @searcher.stubs(:search_filter).returns(:filter) @searcher.stubs(:search_base).returns(:base) @searcher.stubs(:process) @request = stub 'request', :key => "yay" end it "should call the ldapsearch method with the search filter" do @searcher.expects(:search_filter).with("yay").returns("yay's filter") @searcher.expects(:ldapsearch).with("yay's filter") @searcher.find @request end it "should fail if no block is passed to the ldapsearch method" do proc { @searcher.ldapsearch("blah") }.should raise_error(ArgumentError) end it "should use the results of the ldapbase method as the ldap search base" do @searcher.stubs(:search_base).returns("mybase") @connection.expects(:search).with do |*args| args[0].should == "mybase" true end @searcher.find @request end it "should default to the value of the :search_base setting as the result of the ldapbase method" do Puppet[:ldapbase] = "myldapbase" searcher = @ldap_class.new searcher.search_base.should == "myldapbase" end it "should use the results of the :search_attributes method as the list of attributes to return" do @searcher.stubs(:search_attributes).returns(:myattrs) @connection.expects(:search).with do |*args| args[3].should == :myattrs true end @searcher.find @request end it "should use depth 2 when searching" do @connection.expects(:search).with do |*args| args[1].should == 2 true end @searcher.find @request end it "should call process() on the first found entry" do @connection.expects(:search).yields("myresult") @searcher.expects(:process).with("myresult") @searcher.find @request end it "should reconnect and retry the search if there is a failure" do run = false @connection.stubs(:search).with do |*args| if run true else run = true raise "failed" end end.yields("myresult") @searcher.expects(:process).with("myresult") @searcher.find @request end it "should not reconnect on failure more than once" do count = 0 @connection.stubs(:search).with do |*args| count += 1 raise ArgumentError, "yay" end proc { @searcher.find(@request) }.should raise_error(Puppet::Error) count.should == 2 end it "should return true if an entry is found" do @connection.expects(:search).yields("result") - @searcher.ldapsearch("whatever") { |r| }.should be_true + @searcher.ldapsearch("whatever") { |r| }.should be_truthy end end describe "when connecting to ldap", :if => Puppet.features.ldap? do it "should create and start a Util::Ldap::Connection instance" do conn = mock 'connection', :connection => "myconn", :start => nil Puppet::Util::Ldap::Connection.expects(:instance).returns conn @searcher.connection.should == "myconn" end it "should only create the ldap connection when asked for it the first time" do conn = mock 'connection', :connection => "myconn", :start => nil Puppet::Util::Ldap::Connection.expects(:instance).returns conn @searcher.connection end it "should cache the connection" do conn = mock 'connection', :connection => "myconn", :start => nil Puppet::Util::Ldap::Connection.expects(:instance).returns conn @searcher.connection.should equal(@searcher.connection) end end describe "when reconnecting to ldap", :if => (Puppet.features.root? and Facter.value("hostname") == "culain") do it "should reconnect to ldap when connections are lost" end end diff --git a/spec/unit/indirector/msgpack_spec.rb b/spec/unit/indirector/msgpack_spec.rb index 9391f94b0..e2478af30 100755 --- a/spec/unit/indirector/msgpack_spec.rb +++ b/spec/unit/indirector/msgpack_spec.rb @@ -1,191 +1,191 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/files' require 'puppet/indirector/indirector_testing/msgpack' describe Puppet::Indirector::Msgpack, :if => Puppet.features.msgpack? do include PuppetSpec::Files subject { Puppet::IndirectorTesting::Msgpack.new } let :model do Puppet::IndirectorTesting end let :indirection do model.indirection end context "#path" do before :each do Puppet[:server_datadir] = '/sample/datadir/master' Puppet[:client_datadir] = '/sample/datadir/client' end it "uses the :server_datadir setting if this is the master" do Puppet.run_mode.stubs(:master?).returns(true) expected = File.join(Puppet[:server_datadir], 'indirector_testing', 'testing.msgpack') subject.path('testing').should == expected end it "uses the :client_datadir setting if this is not the master" do Puppet.run_mode.stubs(:master?).returns(false) expected = File.join(Puppet[:client_datadir], 'indirector_testing', 'testing.msgpack') subject.path('testing').should == expected end it "overrides the default extension with a supplied value" do Puppet.run_mode.stubs(:master?).returns(true) expected = File.join(Puppet[:server_datadir], 'indirector_testing', 'testing.not-msgpack') subject.path('testing', '.not-msgpack').should == expected end ['../foo', '..\\foo', './../foo', '.\\..\\foo', '/foo', '//foo', '\\foo', '\\\\goo', "test\0/../bar", "test\0\\..\\bar", "..\\/bar", "/tmp/bar", "/tmp\\bar", "tmp\\bar", " / bar", " /../ bar", " \\..\\ bar", "c:\\foo", "c:/foo", "\\\\?\\UNC\\bar", "\\\\foo\\bar", "\\\\?\\c:\\foo", "//?/UNC/bar", "//foo/bar", "//?/c:/foo", ].each do |input| it "should resist directory traversal attacks (#{input.inspect})" do expect { subject.path(input) }.to raise_error ArgumentError, 'invalid key' end end end context "handling requests" do before :each do Puppet.run_mode.stubs(:master?).returns(true) Puppet[:server_datadir] = tmpdir('msgpackdir') FileUtils.mkdir_p(File.join(Puppet[:server_datadir], 'indirector_testing')) end let :file do subject.path(request.key) end def with_content(text) FileUtils.mkdir_p(File.dirname(file)) File.open(file, 'w') {|f| f.write text } yield if block_given? end it "data saves and then loads again correctly" do subject.save(indirection.request(:save, 'example', model.new('banana'))) subject.find(indirection.request(:find, 'example', nil)).value.should == 'banana' end context "#find" do let :request do indirection.request(:find, 'example', nil) end it "returns nil if the file doesn't exist" do subject.find(request).should be_nil end it "raises a descriptive error when the file can't be read" do with_content(model.new('foo').to_msgpack) do # I don't like this, but there isn't a credible alternative that # also works on Windows, so a stub it is. At least the expectation # will fail if the implementation changes. Sorry to the next dev. File.expects(:read).with(file).raises(Errno::EPERM) expect { subject.find(request) }. to raise_error Puppet::Error, /Could not read MessagePack/ end end it "raises a descriptive error when the file content is invalid" do with_content("this is totally invalid MessagePack") do expect { subject.find(request) }. to raise_error Puppet::Error, /Could not parse MessagePack data/ end end it "should return an instance of the indirected object when valid" do with_content(model.new(1).to_msgpack) do instance = subject.find(request) instance.should be_an_instance_of model instance.value.should == 1 end end end context "#save" do let :instance do model.new(4) end let :request do indirection.request(:find, 'example', instance) end it "should save the instance of the request as MessagePack to disk" do subject.save(request) content = File.read(file) MessagePack.unpack(content)['value'].should == 4 end it "should create the indirection directory if required" do target = File.join(Puppet[:server_datadir], 'indirector_testing') Dir.rmdir(target) subject.save(request) File.should be_directory(target) end end context "#destroy" do let :request do indirection.request(:find, 'example', nil) end it "removes an existing file" do with_content('hello') do subject.destroy(request) end - Puppet::FileSystem.exist?(file).should be_false + Puppet::FileSystem.exist?(file).should be_falsey end it "silently succeeds when files don't exist" do Puppet::FileSystem.unlink(file) rescue nil - subject.destroy(request).should be_true + subject.destroy(request).should be_truthy end it "raises an informative error for other failures" do Puppet::FileSystem.stubs(:unlink).with(file).raises(Errno::EPERM, 'fake permission problem') with_content('hello') do expect { subject.destroy(request) }.to raise_error(Puppet::Error) end Puppet::FileSystem.unstub(:unlink) # thanks, mocha end end end context "#search" do before :each do Puppet.run_mode.stubs(:master?).returns(true) Puppet[:server_datadir] = tmpdir('msgpackdir') FileUtils.mkdir_p(File.join(Puppet[:server_datadir], 'indirector_testing')) end def request(glob) indirection.request(:search, glob, nil) end def create_file(name, value = 12) File.open(subject.path(name, ''), 'w') do |f| f.write Puppet::IndirectorTesting.new(value).to_msgpack end end it "returns an empty array when nothing matches the key as a glob" do subject.search(request('*')).should == [] end it "returns an array with one item if one item matches" do create_file('foo.msgpack', 'foo') create_file('bar.msgpack', 'bar') subject.search(request('f*')).map(&:value).should == ['foo'] end it "returns an array of items when more than one item matches" do create_file('foo.msgpack', 'foo') create_file('bar.msgpack', 'bar') create_file('baz.msgpack', 'baz') subject.search(request('b*')).map(&:value).should =~ ['bar', 'baz'] end it "only items with the .msgpack extension" do create_file('foo.msgpack', 'foo-msgpack') create_file('foo.msgpack~', 'foo-backup') subject.search(request('f*')).map(&:value).should == ['foo-msgpack'] end end end diff --git a/spec/unit/indirector/ssl_file_spec.rb b/spec/unit/indirector/ssl_file_spec.rb index 8d13bdc94..c29c8d581 100755 --- a/spec/unit/indirector/ssl_file_spec.rb +++ b/spec/unit/indirector/ssl_file_spec.rb @@ -1,328 +1,328 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector/ssl_file' describe Puppet::Indirector::SslFile do include PuppetSpec::Files before :all do @indirection = stub 'indirection', :name => :testing, :model => @model Puppet::Indirector::Indirection.expects(:instance).with(:testing).returns(@indirection) module Testing; end @file_class = class Testing::MyType < Puppet::Indirector::SslFile self end end before :each do @model = mock 'model' @setting = :certdir @file_class.store_in @setting @file_class.store_at nil @file_class.store_ca_at nil @path = make_absolute("/thisdoesntexist/my_directory") Puppet[:noop] = false Puppet[@setting] = @path Puppet[:trace] = false end after :each do @file_class.store_in nil @file_class.store_at nil @file_class.store_ca_at nil end it "should use :main and :ssl upon initialization" do Puppet.settings.expects(:use).with(:main, :ssl) @file_class.new end it "should return a nil collection directory if no directory setting has been provided" do @file_class.store_in nil @file_class.collection_directory.should be_nil end it "should return a nil file location if no location has been provided" do @file_class.store_at nil @file_class.file_location.should be_nil end it "should fail if no store directory or file location has been set" do Puppet.settings.expects(:use).with(:main, :ssl) @file_class.store_in nil @file_class.store_at nil expect { @file_class.new }.to raise_error(Puppet::DevError, /No file or directory setting provided/) end describe "when managing ssl files" do before do Puppet.settings.stubs(:use) @searcher = @file_class.new @cert = stub 'certificate', :name => "myname" @certpath = File.join(@path, "myname.pem") @request = stub 'request', :key => @cert.name, :instance => @cert end it "should consider the file a ca file if the name is equal to what the SSL::Host class says is the CA name" do Puppet::SSL::Host.expects(:ca_name).returns "amaca" @searcher.should be_ca("amaca") end describe "when choosing the location for certificates" do it "should set them at the ca setting's path if a ca setting is available and the name resolves to the CA name" do @file_class.store_in nil @file_class.store_at :mysetting @file_class.store_ca_at :cakey Puppet[:cakey] = File.expand_path("/ca/file") @searcher.expects(:ca?).with(@cert.name).returns true @searcher.path(@cert.name).should == Puppet[:cakey] end it "should set them at the file location if a file setting is available" do @file_class.store_in nil @file_class.store_at :cacrl Puppet[:cacrl] = File.expand_path("/some/file") @searcher.path(@cert.name).should == Puppet[:cacrl] end it "should set them in the setting directory, with the certificate name plus '.pem', if a directory setting is available" do @searcher.path(@cert.name).should == @certpath end ['../foo', '..\\foo', './../foo', '.\\..\\foo', '/foo', '//foo', '\\foo', '\\\\goo', "test\0/../bar", "test\0\\..\\bar", "..\\/bar", "/tmp/bar", "/tmp\\bar", "tmp\\bar", " / bar", " /../ bar", " \\..\\ bar", "c:\\foo", "c:/foo", "\\\\?\\UNC\\bar", "\\\\foo\\bar", "\\\\?\\c:\\foo", "//?/UNC/bar", "//foo/bar", "//?/c:/foo", ].each do |input| it "should resist directory traversal attacks (#{input.inspect})" do expect { @searcher.path(input) }.to raise_error end end # REVISIT: Should probably test MS-DOS reserved names here, too, since # they would represent a vulnerability on a Win32 system, should we ever # support that path. Don't forget that 'CON.foo' == 'CON' # --daniel 2011-09-24 end describe "when finding certificates on disk" do describe "and no certificate is present" do it "should return nil" do Puppet::FileSystem.expects(:exist?).with(@path).returns(true) Dir.expects(:entries).with(@path).returns([]) Puppet::FileSystem.expects(:exist?).with(@certpath).returns(false) @searcher.find(@request).should be_nil end end describe "and a certificate is present" do let(:cert) { mock 'cert' } let(:model) { mock 'model' } before(:each) do @file_class.stubs(:model).returns model end context "is readable" do it "should return an instance of the model, which it should use to read the certificate" do Puppet::FileSystem.expects(:exist?).with(@certpath).returns true model.expects(:new).with("myname").returns cert cert.expects(:read).with(@certpath) @searcher.find(@request).should equal(cert) end end context "is unreadable" do it "should raise an exception" do Puppet::FileSystem.expects(:exist?).with(@certpath).returns(true) model.expects(:new).with("myname").returns cert cert.expects(:read).with(@certpath).raises(Errno::EACCES) expect { @searcher.find(@request) }.to raise_error(Errno::EACCES) end end end describe "and a certificate is present but has uppercase letters" do before do @request = stub 'request', :key => "myhost" end # This is kind of more an integration test; it's for #1382, until # the support for upper-case certs can be removed around mid-2009. it "should rename the existing file to the lower-case path" do @path = @searcher.path("myhost") Puppet::FileSystem.expects(:exist?).with(@path).returns(false) dir, file = File.split(@path) Puppet::FileSystem.expects(:exist?).with(dir).returns true Dir.expects(:entries).with(dir).returns [".", "..", "something.pem", file.upcase] File.expects(:rename).with(File.join(dir, file.upcase), @path) cert = mock 'cert' model = mock 'model' @searcher.stubs(:model).returns model @searcher.model.expects(:new).with("myhost").returns cert cert.expects(:read).with(@path) @searcher.find(@request) end end end describe "when saving certificates to disk" do before do FileTest.stubs(:directory?).returns true FileTest.stubs(:writable?).returns true end it "should fail if the directory is absent" do FileTest.expects(:directory?).with(File.dirname(@certpath)).returns false lambda { @searcher.save(@request) }.should raise_error(Puppet::Error) end it "should fail if the directory is not writeable" do FileTest.stubs(:directory?).returns true FileTest.expects(:writable?).with(File.dirname(@certpath)).returns false lambda { @searcher.save(@request) }.should raise_error(Puppet::Error) end it "should save to the path the output of converting the certificate to a string" do fh = mock 'filehandle' fh.expects(:print).with("mycert") @searcher.stubs(:write).yields fh @cert.expects(:to_s).returns "mycert" @searcher.save(@request) end describe "and a directory setting is set" do it "should use the Settings class to write the file" do @searcher.class.store_in @setting fh = mock 'filehandle' fh.stubs :print Puppet.settings.setting(@setting).expects(:open_file).with(@certpath, 'w').yields fh @searcher.save(@request) end end describe "and a file location is set" do it "should use the filehandle provided by the Settings" do @searcher.class.store_at @setting fh = mock 'filehandle' fh.stubs :print Puppet.settings.setting(@setting).expects(:open).with('w').yields fh @searcher.save(@request) end end describe "and the name is the CA name and a ca setting is set" do it "should use the filehandle provided by the Settings" do @searcher.class.store_at @setting @searcher.class.store_ca_at :cakey Puppet[:cakey] = "castuff stub" fh = mock 'filehandle' fh.stubs :print Puppet.settings.setting(:cakey).expects(:open).with('w').yields fh @searcher.stubs(:ca?).returns true @searcher.save(@request) end end end describe "when destroying certificates" do describe "that do not exist" do before do Puppet::FileSystem.expects(:exist?).with(Puppet::FileSystem.pathname(@certpath)).returns false end it "should return false" do - @searcher.destroy(@request).should be_false + @searcher.destroy(@request).should be_falsey end end describe "that exist" do it "should unlink the certificate file" do path = Puppet::FileSystem.pathname(@certpath) Puppet::FileSystem.expects(:exist?).with(path).returns true Puppet::FileSystem.expects(:unlink).with(path) @searcher.destroy(@request) end it "should log that is removing the file" do Puppet::FileSystem.stubs(:exist?).returns true Puppet::FileSystem.stubs(:unlink) Puppet.expects(:notice) @searcher.destroy(@request) end end end describe "when searching for certificates" do let(:one) { stub 'one' } let(:two) { stub 'two' } let(:one_path) { File.join(@path, 'one.pem') } let(:two_path) { File.join(@path, 'two.pem') } let(:model) { mock 'model' } before :each do @file_class.stubs(:model).returns model end it "should return a certificate instance for all files that exist" do Dir.expects(:entries).with(@path).returns(%w{. .. one.pem two.pem}) model.expects(:new).with("one").returns one one.expects(:read).with(one_path) model.expects(:new).with("two").returns two two.expects(:read).with(two_path) @searcher.search(@request).should == [one, two] end it "should raise an exception if any file is unreadable" do Dir.expects(:entries).with(@path).returns(%w{. .. one.pem two.pem}) model.expects(:new).with("one").returns(one) one.expects(:read).with(one_path) model.expects(:new).with("two").returns(two) two.expects(:read).raises(Errno::EACCES) expect { @searcher.search(@request) }.to raise_error(Errno::EACCES) end it "should skip any files that do not match /\.pem$/" do Dir.expects(:entries).with(@path).returns(%w{. .. one two.notpem}) model.expects(:new).never @searcher.search(@request).should == [] end end end end diff --git a/spec/unit/interface/action_builder_spec.rb b/spec/unit/interface/action_builder_spec.rb index 0fb64641a..e51c01eac 100755 --- a/spec/unit/interface/action_builder_spec.rb +++ b/spec/unit/interface/action_builder_spec.rb @@ -1,217 +1,217 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/interface' 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) }.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 + action.default.should be_truthy 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 + action.default.should be_falsey 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/face_collection_spec.rb b/spec/unit/interface/face_collection_spec.rb index 89928b70a..596cf9ee7 100755 --- a/spec/unit/interface/face_collection_spec.rb +++ b/spec/unit/interface/face_collection_spec.rb @@ -1,212 +1,212 @@ #! /usr/bin/env ruby require 'spec_helper' require 'tmpdir' require 'puppet/interface' describe Puppet::Interface::FaceCollection do # To prevent conflicts with other specs that use faces, we must save and restore global state. # Because there are specs that do 'describe Puppet::Face[...]', we must restore the same objects otherwise # the 'subject' of the specs will differ. before :all do # Save FaceCollection's global state faces = described_class.instance_variable_get(:@faces) @faces = faces.dup faces.each do |k, v| @faces[k] = v.dup end @faces_loaded = described_class.instance_variable_get(:@loaded) # Save the already required face files @required = [] $".each do |path| @required << path if path =~ /face\/.*\.rb$/ end # Save Autoload's global state @loaded = Puppet::Util::Autoload.instance_variable_get(:@loaded).dup end after :all do # Restore global state subject.instance_variable_set :@faces, @faces subject.instance_variable_set :@loaded, @faces_loaded $".delete_if { |path| path =~ /face\/.*\.rb$/ } @required.each { |path| $".push path unless $".include? path } Puppet::Util::Autoload.instance_variable_set(:@loaded, @loaded) end before :each do # Before each test, clear the faces subject.instance_variable_get(:@faces).clear subject.instance_variable_set(:@loaded, false) Puppet::Util::Autoload.instance_variable_get(:@loaded).clear $".delete_if { |path| path =~ /face\/.*\.rb$/ } 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 + subject["bar", '0.0.1'].should be_truthy 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 + subject["bar", '0.0.1'].should be_falsey 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 + subject["bar", '0.0.1'].should be_falsey 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 + subject[:huzzah, :current].should be_truthy end end it "should not cause an invalid face to be enumerated later" do - subject[:there_is_no_face, :current].should be_false + subject[:there_is_no_face, :current].should be_falsey 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, "23foo", "#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, "foo_bar23" => :foo_bar23, :foo_bar23 => :foo_bar23, } 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) }. 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/module_spec.rb b/spec/unit/module_spec.rb index ce9d7b55e..4f894715c 100755 --- a/spec/unit/module_spec.rb +++ b/spec/unit/module_spec.rb @@ -1,718 +1,718 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/files' require 'puppet_spec/modules' require 'puppet/module_tool/checksums' describe Puppet::Module do include PuppetSpec::Files let(:env) { mock("environment") } let(:path) { "/path" } let(:name) { "mymod" } let(:mod) { Puppet::Module.new(name, path, env) } before do # This is necessary because of the extra checks we have for the deprecated # 'plugins' directory Puppet::FileSystem.stubs(:exist?).returns false end it "should have a class method that returns a named module from a given environment" do env = Puppet::Node::Environment.create(:myenv, []) env.expects(:module).with(name).returns "yep" Puppet.override(:environments => Puppet::Environments::Static.new(env)) do Puppet::Module.find(name, "myenv").should == "yep" end end it "should return nil if asked for a named module that doesn't exist" do env = Puppet::Node::Environment.create(:myenv, []) env.expects(:module).with(name).returns nil Puppet.override(:environments => Puppet::Environments::Static.new(env)) do Puppet::Module.find(name, "myenv").should be_nil end end describe "attributes" do it "should support a 'version' attribute" do mod.version = 1.09 mod.version.should == 1.09 end it "should support a 'source' attribute" do mod.source = "http://foo/bar" mod.source.should == "http://foo/bar" end it "should support a 'project_page' attribute" do mod.project_page = "http://foo/bar" mod.project_page.should == "http://foo/bar" end it "should support an 'author' attribute" do mod.author = "Luke Kanies " mod.author.should == "Luke Kanies " end it "should support a 'license' attribute" do mod.license = "GPL2" mod.license.should == "GPL2" end it "should support a 'summary' attribute" do mod.summary = "GPL2" mod.summary.should == "GPL2" end it "should support a 'description' attribute" do mod.description = "GPL2" mod.description.should == "GPL2" end it "should support specifying a compatible puppet version" do mod.puppetversion = "0.25" mod.puppetversion.should == "0.25" end end it "should validate that the puppet version is compatible" do mod.puppetversion = "0.25" Puppet.expects(:version).returns "0.25" mod.validate_puppet_version end it "should fail if the specified puppet version is not compatible" do mod.puppetversion = "0.25" Puppet.stubs(:version).returns "0.24" lambda { mod.validate_puppet_version }.should raise_error(Puppet::Module::IncompatibleModule) end describe "when finding unmet dependencies" do before do Puppet::FileSystem.unstub(:exist?) @modpath = tmpdir('modpath') Puppet.settings[:modulepath] = @modpath end it "should list modules that are missing" do metadata_file = "#{@modpath}/needy/metadata.json" Puppet::FileSystem.expects(:exist?).with(metadata_file).returns true mod = PuppetSpec::Modules.create( 'needy', @modpath, :metadata => { :dependencies => [{ "version_requirement" => ">= 2.2.0", "name" => "baz/foobar" }] } ) mod.unmet_dependencies.should == [{ :reason => :missing, :name => "baz/foobar", :version_constraint => ">= 2.2.0", :parent => { :name => 'puppetlabs/needy', :version => 'v9.9.9' }, :mod_details => { :installed_version => nil } }] end it "should list modules that are missing and have invalid names" do metadata_file = "#{@modpath}/needy/metadata.json" Puppet::FileSystem.expects(:exist?).with(metadata_file).returns true mod = PuppetSpec::Modules.create( 'needy', @modpath, :metadata => { :dependencies => [{ "version_requirement" => ">= 2.2.0", "name" => "baz/foobar=bar" }] } ) mod.unmet_dependencies.should == [{ :reason => :missing, :name => "baz/foobar=bar", :version_constraint => ">= 2.2.0", :parent => { :name => 'puppetlabs/needy', :version => 'v9.9.9' }, :mod_details => { :installed_version => nil } }] end it "should list modules with unmet version requirement" do env = Puppet::Node::Environment.create(:testing, [@modpath]) ['test_gte_req', 'test_specific_req', 'foobar'].each do |mod_name| metadata_file = "#{@modpath}/#{mod_name}/metadata.json" Puppet::FileSystem.stubs(:exist?).with(metadata_file).returns true end mod = PuppetSpec::Modules.create( 'test_gte_req', @modpath, :metadata => { :dependencies => [{ "version_requirement" => ">= 2.2.0", "name" => "baz/foobar" }] }, :environment => env ) mod2 = PuppetSpec::Modules.create( 'test_specific_req', @modpath, :metadata => { :dependencies => [{ "version_requirement" => "1.0.0", "name" => "baz/foobar" }] }, :environment => env ) PuppetSpec::Modules.create( 'foobar', @modpath, :metadata => { :version => '2.0.0', :author => 'baz' }, :environment => env ) mod.unmet_dependencies.should == [{ :reason => :version_mismatch, :name => "baz/foobar", :version_constraint => ">= 2.2.0", :parent => { :version => "v9.9.9", :name => "puppetlabs/test_gte_req" }, :mod_details => { :installed_version => "2.0.0" } }] mod2.unmet_dependencies.should == [{ :reason => :version_mismatch, :name => "baz/foobar", :version_constraint => "v1.0.0", :parent => { :version => "v9.9.9", :name => "puppetlabs/test_specific_req" }, :mod_details => { :installed_version => "2.0.0" } }] end it "should consider a dependency without a version requirement to be satisfied" do env = Puppet::Node::Environment.create(:testing, [@modpath]) mod = PuppetSpec::Modules.create( 'foobar', @modpath, :metadata => { :dependencies => [{ "name" => "baz/foobar" }] }, :environment => env ) PuppetSpec::Modules.create( 'foobar', @modpath, :metadata => { :version => '2.0.0', :author => 'baz' }, :environment => env ) mod.unmet_dependencies.should be_empty end it "should consider a dependency without a semantic version to be unmet" do env = Puppet::Node::Environment.create(:testing, [@modpath]) metadata_file = "#{@modpath}/foobar/metadata.json" Puppet::FileSystem.expects(:exist?).with(metadata_file).times(3).returns true mod = PuppetSpec::Modules.create( 'foobar', @modpath, :metadata => { :dependencies => [{ "name" => "baz/foobar" }] }, :environment => env ) PuppetSpec::Modules.create( 'foobar', @modpath, :metadata => { :version => '5.1', :author => 'baz' }, :environment => env ) mod.unmet_dependencies.should == [{ :reason => :non_semantic_version, :parent => { :version => "v9.9.9", :name => "puppetlabs/foobar" }, :mod_details => { :installed_version => "5.1" }, :name => "baz/foobar", :version_constraint => ">= 0.0.0" }] end it "should have valid dependencies when no dependencies have been specified" do mod = PuppetSpec::Modules.create( 'foobar', @modpath, :metadata => { :dependencies => [] } ) mod.unmet_dependencies.should == [] end it "should only list unmet dependencies" do env = Puppet::Node::Environment.create(:testing, [@modpath]) [name, 'satisfied'].each do |mod_name| metadata_file = "#{@modpath}/#{mod_name}/metadata.json" Puppet::FileSystem.expects(:exist?).with(metadata_file).twice.returns true end mod = PuppetSpec::Modules.create( name, @modpath, :metadata => { :dependencies => [ { "version_requirement" => ">= 2.2.0", "name" => "baz/satisfied" }, { "version_requirement" => ">= 2.2.0", "name" => "baz/notsatisfied" } ] }, :environment => env ) PuppetSpec::Modules.create( 'satisfied', @modpath, :metadata => { :version => '3.3.0', :author => 'baz' }, :environment => env ) mod.unmet_dependencies.should == [{ :reason => :missing, :mod_details => { :installed_version => nil }, :parent => { :version => "v9.9.9", :name => "puppetlabs/#{name}" }, :name => "baz/notsatisfied", :version_constraint => ">= 2.2.0" }] end it "should be empty when all dependencies are met" do env = Puppet::Node::Environment.create(:testing, [@modpath]) mod = PuppetSpec::Modules.create( 'mymod2', @modpath, :metadata => { :dependencies => [ { "version_requirement" => ">= 2.2.0", "name" => "baz/satisfied" }, { "version_requirement" => "< 2.2.0", "name" => "baz/alsosatisfied" } ] }, :environment => env ) PuppetSpec::Modules.create( 'satisfied', @modpath, :metadata => { :version => '3.3.0', :author => 'baz' }, :environment => env ) PuppetSpec::Modules.create( 'alsosatisfied', @modpath, :metadata => { :version => '2.1.0', :author => 'baz' }, :environment => env ) mod.unmet_dependencies.should be_empty end end describe "when managing supported platforms" do it "should support specifying a supported platform" do mod.supports "solaris" end it "should support specifying a supported platform and version" do mod.supports "solaris", 1.0 end end it "should return nil if asked for a module whose name is 'nil'" do Puppet::Module.find(nil, "myenv").should be_nil end it "should provide support for logging" do Puppet::Module.ancestors.should be_include(Puppet::Util::Logging) end it "should be able to be converted to a string" do mod.to_s.should == "Module #{name}(#{path})" end it "should fail if its name is not alphanumeric" do lambda { Puppet::Module.new(".something", "/path", env) }.should raise_error(Puppet::Module::InvalidName) end it "should require a name at initialization" do lambda { Puppet::Module.new }.should raise_error(ArgumentError) end it "should accept an environment at initialization" do Puppet::Module.new("foo", "/path", env).environment.should == env end describe '#modulepath' do it "should return the directory the module is installed in, if a path exists" do mod = Puppet::Module.new("foo", "/a/foo", env) mod.modulepath.should == '/a' end end [:plugins, :pluginfacts, :templates, :files, :manifests].each do |filetype| case filetype when :plugins dirname = "lib" when :pluginfacts dirname = "facts.d" else dirname = filetype.to_s end it "should be able to return individual #{filetype}" do module_file = File.join(path, dirname, "my/file") Puppet::FileSystem.expects(:exist?).with(module_file).returns true mod.send(filetype.to_s.sub(/s$/, ''), "my/file").should == module_file end it "should consider #{filetype} to be present if their base directory exists" do module_file = File.join(path, dirname) Puppet::FileSystem.expects(:exist?).with(module_file).returns true - mod.send(filetype.to_s + "?").should be_true + mod.send(filetype.to_s + "?").should be_truthy end it "should consider #{filetype} to be absent if their base directory does not exist" do module_file = File.join(path, dirname) Puppet::FileSystem.expects(:exist?).with(module_file).returns false - mod.send(filetype.to_s + "?").should be_false + mod.send(filetype.to_s + "?").should be_falsey end it "should return nil if asked to return individual #{filetype} that don't exist" do module_file = File.join(path, dirname, "my/file") Puppet::FileSystem.expects(:exist?).with(module_file).returns false mod.send(filetype.to_s.sub(/s$/, ''), "my/file").should be_nil end it "should return the base directory if asked for a nil path" do base = File.join(path, dirname) Puppet::FileSystem.expects(:exist?).with(base).returns true mod.send(filetype.to_s.sub(/s$/, ''), nil).should == base end end it "should return the path to the plugin directory" do mod.plugin_directory.should == File.join(path, "lib") end end describe Puppet::Module, "when finding matching manifests" do before do @mod = Puppet::Module.new("mymod", "/a", mock("environment")) @pq_glob_with_extension = "yay/*.xx" @fq_glob_with_extension = "/a/manifests/#{@pq_glob_with_extension}" end it "should return all manifests matching the glob pattern" do Dir.expects(:glob).with(@fq_glob_with_extension).returns(%w{foo bar}) FileTest.stubs(:directory?).returns false @mod.match_manifests(@pq_glob_with_extension).should == %w{foo bar} end it "should not return directories" do Dir.expects(:glob).with(@fq_glob_with_extension).returns(%w{foo bar}) FileTest.expects(:directory?).with("foo").returns false FileTest.expects(:directory?).with("bar").returns true @mod.match_manifests(@pq_glob_with_extension).should == %w{foo} end it "should default to the 'init' file if no glob pattern is specified" do Puppet::FileSystem.expects(:exist?).with("/a/manifests/init.pp").returns(true) @mod.match_manifests(nil).should == %w{/a/manifests/init.pp} end it "should return all manifests matching the glob pattern in all existing paths" do Dir.expects(:glob).with(@fq_glob_with_extension).returns(%w{a b}) @mod.match_manifests(@pq_glob_with_extension).should == %w{a b} end it "should match the glob pattern plus '.pp' if no extension is specified" do Dir.expects(:glob).with("/a/manifests/yay/foo.pp").returns(%w{yay}) @mod.match_manifests("yay/foo").should == %w{yay} end it "should return an empty array if no manifests matched" do Dir.expects(:glob).with(@fq_glob_with_extension).returns([]) @mod.match_manifests(@pq_glob_with_extension).should == [] end it "should raise an error if the pattern tries to leave the manifest directory" do expect do @mod.match_manifests("something/../../*") end.to raise_error(Puppet::Module::InvalidFilePattern, 'The pattern "something/../../*" to find manifests in the module "mymod" is invalid and potentially unsafe.') end end describe Puppet::Module do include PuppetSpec::Files before do @modpath = tmpdir('modpath') @module = PuppetSpec::Modules.create('mymod', @modpath) end it "should use 'License' in its current path as its metadata file" do @module.license_file.should == "#{@modpath}/mymod/License" end it "should cache the license file" do @module.expects(:path).once.returns nil @module.license_file @module.license_file end it "should use 'metadata.json' in its current path as its metadata file" do @module.metadata_file.should == "#{@modpath}/mymod/metadata.json" end it "should have metadata if it has a metadata file and its data is not empty" do Puppet::FileSystem.expects(:exist?).with(@module.metadata_file).returns true File.stubs(:read).with(@module.metadata_file).returns "{\"foo\" : \"bar\"}" @module.should be_has_metadata end it "should have metadata if it has a metadata file and its data is not empty" do Puppet::FileSystem.expects(:exist?).with(@module.metadata_file).returns true File.stubs(:read).with(@module.metadata_file).returns "{\"foo\" : \"bar\"}" @module.should be_has_metadata end it "should not have metadata if has a metadata file and its data is empty" do Puppet::FileSystem.expects(:exist?).with(@module.metadata_file).returns true File.stubs(:read).with(@module.metadata_file).returns "/* +-----------------------------------------------------------------------+ | | | ==> DO NOT EDIT THIS FILE! <== | | | | You should edit the `Modulefile` and run `puppet-module build` | | to generate the `metadata.json` file for your releases. | | | +-----------------------------------------------------------------------+ */ {}" @module.should_not be_has_metadata end it "should know if it is missing a metadata file" do Puppet::FileSystem.expects(:exist?).with(@module.metadata_file).returns false @module.should_not be_has_metadata end it "should be able to parse its metadata file" do @module.should respond_to(:load_metadata) end it "should parse its metadata file on initialization if it is present" do Puppet::Module.any_instance.expects(:has_metadata?).returns true Puppet::Module.any_instance.expects(:load_metadata) Puppet::Module.new("yay", "/path", mock("env")) end it "should tolerate failure to parse" do Puppet::FileSystem.expects(:exist?).with(@module.metadata_file).returns true File.stubs(:read).with(@module.metadata_file).returns(my_fixture('trailing-comma.json')) - @module.has_metadata?.should be_false + @module.has_metadata?.should be_falsey end def a_module_with_metadata(data) text = data.to_pson mod = Puppet::Module.new("foo", "/path", mock("env")) mod.stubs(:metadata_file).returns "/my/file" File.stubs(:read).with("/my/file").returns text mod end describe "when loading the metadata file" do before do @data = { :license => "GPL2", :author => "luke", :version => "1.0", :source => "http://foo/", :puppetversion => "0.25", :dependencies => [] } @module = a_module_with_metadata(@data) end %w{source author version license}.each do |attr| it "should set #{attr} if present in the metadata file" do @module.load_metadata @module.send(attr).should == @data[attr.to_sym] end it "should fail if #{attr} is not present in the metadata file" do @data.delete(attr.to_sym) @text = @data.to_pson File.stubs(:read).with("/my/file").returns @text lambda { @module.load_metadata }.should raise_error( Puppet::Module::MissingMetadata, "No #{attr} module metadata provided for foo" ) end end it "should set puppetversion if present in the metadata file" do @module.load_metadata @module.puppetversion.should == @data[:puppetversion] end context "when versionRequirement is used for dependency version info" do before do @data = { :license => "GPL2", :author => "luke", :version => "1.0", :source => "http://foo/", :puppetversion => "0.25", :dependencies => [ { "versionRequirement" => "0.0.1", "name" => "pmtacceptance/stdlib" }, { "versionRequirement" => "0.1.0", "name" => "pmtacceptance/apache" } ] } @module = a_module_with_metadata(@data) end it "should set the dependency version_requirement key" do @module.load_metadata @module.dependencies[0]['version_requirement'].should == "0.0.1" end it "should set the version_requirement key for all dependencies" do @module.load_metadata @module.dependencies[0]['version_requirement'].should == "0.0.1" @module.dependencies[1]['version_requirement'].should == "0.1.0" end end end it "should be able to tell if there are local changes" do modpath = tmpdir('modpath') foo_checksum = 'acbd18db4cc2f85cedef654fccc4a4d8' checksummed_module = PuppetSpec::Modules.create( 'changed', modpath, :metadata => { :checksums => { "foo" => foo_checksum, } } ) foo_path = Pathname.new(File.join(checksummed_module.path, 'foo')) IO.binwrite(foo_path, 'notfoo') - Puppet::ModuleTool::Checksums.new(foo_path).checksum(foo_path).should_not == foo_checksum + expect(Puppet::ModuleTool::Checksums.new(foo_path).checksum(foo_path)).not_to eq(foo_checksum) IO.binwrite(foo_path, 'foo') - Puppet::ModuleTool::Checksums.new(foo_path).checksum(foo_path).should == foo_checksum + expect(Puppet::ModuleTool::Checksums.new(foo_path).checksum(foo_path)).not_to eq(foo_checksum) end it "should know what other modules require it" do env = Puppet::Node::Environment.create(:testing, [@modpath]) dependable = PuppetSpec::Modules.create( 'dependable', @modpath, :metadata => {:author => 'puppetlabs'}, :environment => env ) PuppetSpec::Modules.create( 'needy', @modpath, :metadata => { :author => 'beggar', :dependencies => [{ "version_requirement" => ">= 2.2.0", "name" => "puppetlabs/dependable" }] }, :environment => env ) PuppetSpec::Modules.create( 'wantit', @modpath, :metadata => { :author => 'spoiled', :dependencies => [{ "version_requirement" => "< 5.0.0", "name" => "puppetlabs/dependable" }] }, :environment => env ) dependable.required_by.should =~ [ { "name" => "beggar/needy", "version" => "9.9.9", "version_requirement" => ">= 2.2.0" }, { "name" => "spoiled/wantit", "version" => "9.9.9", "version_requirement" => "< 5.0.0" } ] end end diff --git a/spec/unit/module_tool_spec.rb b/spec/unit/module_tool_spec.rb index 0eafb2e7d..f21acc9c5 100755 --- a/spec/unit/module_tool_spec.rb +++ b/spec/unit/module_tool_spec.rb @@ -1,327 +1,327 @@ #! /usr/bin/env ruby # encoding: UTF-8 require 'spec_helper' require 'puppet/module_tool' describe Puppet::ModuleTool do describe '.is_module_root?' do it 'should return true if directory has a Modulefile file' do FileTest.expects(:file?).with(responds_with(:to_s, '/a/b/c/metadata.json')). returns(false) FileTest.expects(:file?).with(responds_with(:to_s, '/a/b/c/Modulefile')). returns(true) - subject.is_module_root?(Pathname.new('/a/b/c')).should be_true + subject.is_module_root?(Pathname.new('/a/b/c')).should be_truthy end it 'should return true if directory has a metadata.json file' do FileTest.expects(:file?).with(responds_with(:to_s, '/a/b/c/metadata.json')). returns(true) - subject.is_module_root?(Pathname.new('/a/b/c')).should be_true + subject.is_module_root?(Pathname.new('/a/b/c')).should be_truthy end it 'should return false if directory does not have a metadata.json or a Modulefile file' do FileTest.expects(:file?).with(responds_with(:to_s, '/a/b/c/metadata.json')). returns(false) FileTest.expects(:file?).with(responds_with(:to_s, '/a/b/c/Modulefile')). returns(false) - subject.is_module_root?(Pathname.new('/a/b/c')).should be_false + subject.is_module_root?(Pathname.new('/a/b/c')).should be_falsey end end describe '.find_module_root' do let(:sample_path) { Pathname.new('/a/b/c').expand_path } it 'should return the first path as a pathname when it contains a module file' do Puppet::ModuleTool.expects(:is_module_root?).with(sample_path). returns(true) subject.find_module_root(sample_path).should == sample_path end it 'should return a parent path as a pathname when it contains a module file' do Puppet::ModuleTool.expects(:is_module_root?). with(responds_with(:to_s, File.expand_path('/a/b/c'))).returns(false) Puppet::ModuleTool.expects(:is_module_root?). with(responds_with(:to_s, File.expand_path('/a/b'))).returns(true) subject.find_module_root(sample_path).should == Pathname.new('/a/b').expand_path end it 'should return nil when no module root can be found' do Puppet::ModuleTool.expects(:is_module_root?).at_least_once.returns(false) subject.find_module_root(sample_path).should be_nil end end describe '.format_tree' do it 'should return an empty tree when given an empty list' do subject.format_tree([]).should == '' end it 'should return a shallow when given a list without dependencies' do list = [ { :text => 'first' }, { :text => 'second' }, { :text => 'third' } ] subject.format_tree(list).should == <<-TREE ├── first ├── second └── third TREE end it 'should return a deeply nested tree when given a list with deep dependencies' do list = [ { :text => 'first', :dependencies => [ { :text => 'second', :dependencies => [ { :text => 'third' } ] } ] }, ] subject.format_tree(list).should == <<-TREE └─┬ first └─┬ second └── third TREE end it 'should show connectors when deep dependencies are not on the last node of the top level' do list = [ { :text => 'first', :dependencies => [ { :text => 'second', :dependencies => [ { :text => 'third' } ] } ] }, { :text => 'fourth' } ] subject.format_tree(list).should == <<-TREE ├─┬ first │ └─┬ second │ └── third └── fourth TREE end it 'should show connectors when deep dependencies are not on the last node of any level' do list = [ { :text => 'first', :dependencies => [ { :text => 'second', :dependencies => [ { :text => 'third' } ] }, { :text => 'fourth' } ] } ] subject.format_tree(list).should == <<-TREE └─┬ first ├─┬ second │ └── third └── fourth TREE end it 'should show connectors in every case when deep dependencies are not on the last node' do list = [ { :text => 'first', :dependencies => [ { :text => 'second', :dependencies => [ { :text => 'third' } ] }, { :text => 'fourth' } ] }, { :text => 'fifth' } ] subject.format_tree(list).should == <<-TREE ├─┬ first │ ├─┬ second │ │ └── third │ └── fourth └── fifth TREE end end describe '.set_option_defaults' do let(:options) { {} } let(:modulepath) { ['/env/module/path', '/global/module/path'] } let(:environment_name) { :current_environment } let(:environment) { Puppet::Node::Environment.create(environment_name, modulepath) } subject do described_class.set_option_defaults(options) options end around do |example| envs = Puppet::Environments::Static.new(environment) Puppet.override(:environments => envs) do example.run end end describe ':environment' do context 'as String' do let(:options) { { :environment => "#{environment_name}" } } it 'assigns the environment with the given name to :environment_instance' do expect(subject).to include :environment_instance => environment end end context 'as Symbol' do let(:options) { { :environment => :"#{environment_name}" } } it 'assigns the environment with the given name to :environment_instance' do expect(subject).to include :environment_instance => environment end end context 'as Puppet::Node::Environment' do let(:env) { Puppet::Node::Environment.create('anonymous', []) } let(:options) { { :environment => env } } it 'assigns the given environment to :environment_instance' do expect(subject).to include :environment_instance => env end end end describe ':modulepath' do let(:options) do { :modulepath => %w[bar foo baz].join(File::PATH_SEPARATOR) } end let(:paths) { options[:modulepath].split(File::PATH_SEPARATOR).map { |dir| File.expand_path(dir) } } it 'is expanded to an absolute path' do expect(subject[:environment_instance].full_modulepath).to eql paths end it 'is used to compute :target_dir' do expect(subject).to include :target_dir => paths.first end context 'conflicts with :environment' do let(:options) do { :modulepath => %w[bar foo baz].join(File::PATH_SEPARATOR), :environment => environment_name } end it 'replaces the modulepath of the :environment_instance' do expect(subject[:environment_instance].full_modulepath).to eql paths end it 'is used to compute :target_dir' do expect(subject).to include :target_dir => paths.first end end end describe ':target_dir' do let(:options) do { :target_dir => 'foo' } end let(:target) { File.expand_path(options[:target_dir]) } it 'is expanded to an absolute path' do expect(subject).to include :target_dir => target end it 'is prepended to the modulepath of the :environment_instance' do expect(subject[:environment_instance].full_modulepath.first).to eql target end context 'conflicts with :modulepath' do let(:options) do { :target_dir => 'foo', :modulepath => %w[bar foo baz].join(File::PATH_SEPARATOR) } end it 'is prepended to the modulepath of the :environment_instance' do expect(subject[:environment_instance].full_modulepath.first).to eql target end it 'shares the provided :modulepath via the :environment_instance' do paths = %w[foo] + options[:modulepath].split(File::PATH_SEPARATOR) paths.map! { |dir| File.expand_path(dir) } expect(subject[:environment_instance].full_modulepath).to eql paths end end context 'conflicts with :environment' do let(:options) do { :target_dir => 'foo', :environment => environment_name } end it 'is prepended to the modulepath of the :environment_instance' do expect(subject[:environment_instance].full_modulepath.first).to eql target end it 'shares the provided :modulepath via the :environment_instance' do paths = %w[foo] + environment.full_modulepath paths.map! { |dir| File.expand_path(dir) } expect(subject[:environment_instance].full_modulepath).to eql paths end end context 'when not passed' do it 'is populated with the first component of the modulepath' do expect(subject).to include :target_dir => subject[:environment_instance].full_modulepath.first end end end end describe '.parse_module_dependency' do it 'parses a dependency without a version range expression' do name, range, expr = subject.parse_module_dependency('source', 'name' => 'foo-bar') expect(name).to eql('foo-bar') expect(range).to eql(Semantic::VersionRange.parse('>= 0.0.0')) expect(expr).to eql('>= 0.0.0') end it 'parses a dependency with a version range expression' do name, range, expr = subject.parse_module_dependency('source', 'name' => 'foo-bar', 'version_requirement' => '1.2.x') expect(name).to eql('foo-bar') expect(range).to eql(Semantic::VersionRange.parse('1.2.x')) expect(expr).to eql('1.2.x') end it 'parses a dependency with a version range expression in the (deprecated) versionRange key' do name, range, expr = subject.parse_module_dependency('source', 'name' => 'foo-bar', 'versionRequirement' => '1.2.x') expect(name).to eql('foo-bar') expect(range).to eql(Semantic::VersionRange.parse('1.2.x')) expect(expr).to eql('1.2.x') end it 'does not raise an error on invalid version range expressions' do name, range, expr = subject.parse_module_dependency('source', 'name' => 'foo-bar', 'version_requirement' => 'nope') expect(name).to eql('foo-bar') expect(range).to eql(Semantic::VersionRange::EMPTY_RANGE) expect(expr).to eql('nope') end end end diff --git a/spec/unit/network/authconfig_spec.rb b/spec/unit/network/authconfig_spec.rb index 3795ed4a7..928338a53 100755 --- a/spec/unit/network/authconfig_spec.rb +++ b/spec/unit/network/authconfig_spec.rb @@ -1,109 +1,109 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/network/authconfig' describe Puppet::Network::AuthConfig do before :each do Puppet::FileSystem.stubs(:stat).returns stub('stat', :ctime => :now) Time.stubs(:now).returns Time.now Puppet::Network::AuthConfig.any_instance.stubs(:exists?).returns(true) # FIXME @authconfig = Puppet::Network::AuthConfig.new("dummy") end describe "when initializing" do it "inserts default ACLs after setting initial rights" do Puppet::Network::AuthConfig.any_instance.expects(:insert_default_acl) Puppet::Network::AuthConfig.new end end describe "when defining an acl with mk_acl" do before :each do Puppet::Network::AuthConfig.any_instance.stubs(:insert_default_acl) @authconfig = Puppet::Network::AuthConfig.new end it "should create a new right for each default acl" do @authconfig.mk_acl(:acl => '/') @authconfig.rights['/'].should be end it "allows everyone for each default right" do @authconfig.mk_acl(:acl => '/') @authconfig.rights['/'].should be_globalallow end it "accepts an argument to restrict the method" do @authconfig.mk_acl(:acl => '/', :method => :find) @authconfig.rights['/'].methods.should == [:find] end it "creates rights with authentication set to true by default" do @authconfig.mk_acl(:acl => '/') - @authconfig.rights['/'].authentication.should be_true + @authconfig.rights['/'].authentication.should be_truthy end it "accepts an argument to set the authentication requirement" do @authconfig.mk_acl(:acl => '/', :authenticated => :any) - @authconfig.rights['/'].authentication.should be_false + @authconfig.rights['/'].authentication.should be_falsey end end describe "when adding default ACLs" do before :each do Puppet::Network::AuthConfig.any_instance.stubs(:insert_default_acl) @authconfig = Puppet::Network::AuthConfig.new Puppet::Network::AuthConfig.any_instance.unstub(:insert_default_acl) end Puppet::Network::AuthConfig::default_acl.each do |acl| it "should create a default right for #{acl[:acl]}" do @authconfig.stubs(:mk_acl) @authconfig.expects(:mk_acl).with(acl) @authconfig.insert_default_acl end end it "should log at info loglevel" do Puppet.expects(:info).at_least_once @authconfig.insert_default_acl end it "creates an empty catch-all rule for '/' for any authentication request state" do @authconfig.stubs(:mk_acl) @authconfig.insert_default_acl @authconfig.rights['/'].should be_empty - @authconfig.rights['/'].authentication.should be_false + @authconfig.rights['/'].authentication.should be_falsey end it '(CVE-2013-2275) allows report submission only for the node matching the certname by default' do acl = { :acl => "~ ^#{Puppet::Network::HTTP::MASTER_URL_PREFIX}\/v3\/report\/([^\/]+)$", :method => :save, :allow => '$1', :authenticated => true } @authconfig.stubs(:mk_acl) @authconfig.expects(:mk_acl).with(acl) @authconfig.insert_default_acl end end describe "when checking authorization" do it "should ask for authorization to the ACL subsystem" do params = { :ip => "127.0.0.1", :node => "me", :environment => :env, :authenticated => true } Puppet::Network::Rights.any_instance.expects(:is_request_forbidden_and_why?).with(:save, "/path/to/resource", params) described_class.new.check_authorization(:save, "/path/to/resource", params) end end end diff --git a/spec/unit/network/authstore_spec.rb b/spec/unit/network/authstore_spec.rb index 75ea2c13e..460b6b6d8 100755 --- a/spec/unit/network/authstore_spec.rb +++ b/spec/unit/network/authstore_spec.rb @@ -1,424 +1,424 @@ #! /usr/bin/env ruby require 'spec_helper' require 'rbconfig' require 'puppet/network/authconfig' describe Puppet::Network::AuthStore do before :each do @authstore = Puppet::Network::AuthStore.new @authstore.reset_interpolation end describe "when checking if the acl has some entries" do it "should be empty if no ACE have been entered" do @authstore.should be_empty end it "should not be empty if it is a global allow" do @authstore.allow('*') @authstore.should_not be_empty end it "should not be empty if at least one allow has been entered" do @authstore.allow_ip('1.1.1.*') @authstore.should_not be_empty end it "should not be empty if at least one deny has been entered" do @authstore.deny_ip('1.1.1.*') @authstore.should_not be_empty end end describe "when checking global allow" do it "should not be enabled by default" do @authstore.should_not be_globalallow @authstore.should_not be_allowed('foo.bar.com', '192.168.1.1') end it "should always allow when enabled" do @authstore.allow('*') @authstore.should be_globalallow @authstore.should be_allowed('foo.bar.com', '192.168.1.1') end end describe "when checking a regex type of allow" do before :each do @authstore.allow('/^(test-)?host[0-9]+\.other-domain\.(com|org|net)$|some-domain\.com/') @ip = '192.168.1.1' end ['host5.other-domain.com', 'test-host12.other-domain.net', 'foo.some-domain.com'].each { |name| it "should allow the host #{name}" do @authstore.should be_allowed(name, @ip) end } ['host0.some-other-domain.com',''].each { |name| it "should not allow the host #{name}" do @authstore.should_not be_allowed(name, @ip) end } end end describe Puppet::Network::AuthStore::Declaration do ['100.101.99.98','100.100.100.100','1.2.3.4','11.22.33.44'].each { |ip| describe "when the pattern is a simple numeric IP such as #{ip}" do before :each do @declaration = Puppet::Network::AuthStore::Declaration.new(:allow_ip,ip) end it "should match the specified IP" do @declaration.should be_match('www.testsite.org',ip) end it "should not match other IPs" do @declaration.should_not be_match('www.testsite.org','200.101.99.98') end end (1..3).each { |n| describe "when the pattern is an IP mask with #{n} numeric segments and a *" do before :each do @ip_pattern = ip.split('.')[0,n].join('.')+'.*' @declaration = Puppet::Network::AuthStore::Declaration.new(:allow_ip,@ip_pattern) end it "should match an IP in the range" do @declaration.should be_match('www.testsite.org',ip) end it "should not match other IPs" do @declaration.should_not be_match('www.testsite.org','200.101.99.98') end it "should not match IPs that differ in the last non-wildcard segment" do other = ip.split('.') other[n-1].succ! @declaration.should_not be_match('www.testsite.org',other.join('.')) end end } } describe "when the pattern is a numeric IP with a back reference" do pending("implementation of backreferences for IP") do before :each do @ip = '100.101.$1' @declaration = Puppet::Network::AuthStore::Declaration.new(:allow_ip,@ip).interpolate('12.34'.match(/(.*)/)) end it "should match an IP with the appropriate interpolation" do @declaration.should be_match('www.testsite.org',@ip.sub(/\$1/,'12.34')) end it "should not match other IPs" do @declaration.should_not be_match('www.testsite.org',@ip.sub(/\$1/,'66.34')) end end end [ "02001:0000:1234:0000:0000:C1C0:ABCD:0876", "2001:0000:1234:0000:00001:C1C0:ABCD:0876", " 2001:0000:1234:0000:0000:C1C0:ABCD:0876 0", "2001:0000:1234: 0000:0000:C1C0:ABCD:0876", "3ffe:0b00:0000:0001:0000:0000:000a", "FF02:0000:0000:0000:0000:0000:0000:0000:0001", "3ffe:b00::1::a", "1:2:3::4:5::7:8", "12345::6:7:8", "1::5:400.2.3.4", "1::5:260.2.3.4", "1::5:256.2.3.4", "1::5:1.256.3.4", "1::5:1.2.256.4", "1::5:1.2.3.256", "1::5:300.2.3.4", "1::5:1.300.3.4", "1::5:1.2.300.4", "1::5:1.2.3.300", "1::5:900.2.3.4", "1::5:1.900.3.4", "1::5:1.2.900.4", "1::5:1.2.3.900", "1::5:300.300.300.300", "1::5:3000.30.30.30", "1::400.2.3.4", "1::260.2.3.4", "1::256.2.3.4", "1::1.256.3.4", "1::1.2.256.4", "1::1.2.3.256", "1::300.2.3.4", "1::1.300.3.4", "1::1.2.300.4", "1::1.2.3.300", "1::900.2.3.4", "1::1.900.3.4", "1::1.2.900.4", "1::1.2.3.900", "1::300.300.300.300", "1::3000.30.30.30", "::400.2.3.4", "::260.2.3.4", "::256.2.3.4", "::1.256.3.4", "::1.2.256.4", "::1.2.3.256", "::300.2.3.4", "::1.300.3.4", "::1.2.300.4", "::1.2.3.300", "::900.2.3.4", "::1.900.3.4", "::1.2.900.4", "::1.2.3.900", "::300.300.300.300", "::3000.30.30.30", "2001:DB8:0:0:8:800:200C:417A:221", # unicast, full "FF01::101::2" # multicast, compressed ].each { |invalid_ip| describe "when the pattern is an invalid IPv6 address such as #{invalid_ip}" do it "should raise an exception" do lambda { Puppet::Network::AuthStore::Declaration.new(:allow,invalid_ip) }.should raise_error end end } [ "1.2.3.4", "2001:0000:1234:0000:0000:C1C0:ABCD:0876", "3ffe:0b00:0000:0000:0001:0000:0000:000a", "FF02:0000:0000:0000:0000:0000:0000:0001", "0000:0000:0000:0000:0000:0000:0000:0001", "0000:0000:0000:0000:0000:0000:0000:0000", "::ffff:192.168.1.26", "2::10", "ff02::1", "fe80::", "2002::", "2001:db8::", "2001:0db8:1234::", "::ffff:0:0", "::1", "::ffff:192.168.1.1", "1:2:3:4:5:6:7:8", "1:2:3:4:5:6::8", "1:2:3:4:5::8", "1:2:3:4::8", "1:2:3::8", "1:2::8", "1::8", "1::2:3:4:5:6:7", "1::2:3:4:5:6", "1::2:3:4:5", "1::2:3:4", "1::2:3", "1::8", "::2:3:4:5:6:7", "::2:3:4:5:6", "::2:3:4:5", "::2:3:4", "::2:3", "::8", "1:2:3:4:5:6::", "1:2:3:4:5::", "1:2:3:4::", "1:2:3::", "1:2::", "1::", "1:2:3:4:5::7:8", "1:2:3:4::7:8", "1:2:3::7:8", "1:2::7:8", "1::7:8", "1:2:3:4:5:6:1.2.3.4", "1:2:3:4:5::1.2.3.4", "1:2:3:4::1.2.3.4", "1:2:3::1.2.3.4", "1:2::1.2.3.4", "1::1.2.3.4", "1:2:3:4::5:1.2.3.4", "1:2:3::5:1.2.3.4", "1:2::5:1.2.3.4", "1::5:1.2.3.4", "1::5:11.22.33.44", "fe80::217:f2ff:254.7.237.98", "fe80::217:f2ff:fe07:ed62", "2001:DB8:0:0:8:800:200C:417A", # unicast, full "FF01:0:0:0:0:0:0:101", # multicast, full "0:0:0:0:0:0:0:1", # loopback, full "0:0:0:0:0:0:0:0", # unspecified, full "2001:DB8::8:800:200C:417A", # unicast, compressed "FF01::101", # multicast, compressed "::1", # loopback, compressed, non-routable "::", # unspecified, compressed, non-routable "0:0:0:0:0:0:13.1.68.3", # IPv4-compatible IPv6 address, full, deprecated "0:0:0:0:0:FFFF:129.144.52.38", # IPv4-mapped IPv6 address, full "::13.1.68.3", # IPv4-compatible IPv6 address, compressed, deprecated "::FFFF:129.144.52.38", # IPv4-mapped IPv6 address, compressed "2001:0DB8:0000:CD30:0000:0000:0000:0000/60", # full, with prefix "2001:0DB8::CD30:0:0:0:0/60", # compressed, with prefix "2001:0DB8:0:CD30::/60", # compressed, with prefix #2 "::/128", # compressed, unspecified address type, non-routable "::1/128", # compressed, loopback address type, non-routable "FF00::/8", # compressed, multicast address type "FE80::/10", # compressed, link-local unicast, non-routable "FEC0::/10", # compressed, site-local unicast, deprecated "127.0.0.1", # standard IPv4, loopback, non-routable "0.0.0.0", # standard IPv4, unspecified, non-routable "255.255.255.255", # standard IPv4 "fe80:0000:0000:0000:0204:61ff:fe9d:f156", "fe80:0:0:0:204:61ff:fe9d:f156", "fe80::204:61ff:fe9d:f156", "fe80:0000:0000:0000:0204:61ff:254.157.241.086", "fe80:0:0:0:204:61ff:254.157.241.86", "fe80::204:61ff:254.157.241.86", "::1", "fe80::", "fe80::1" ].each { |ip| describe "when the pattern is a valid IP such as #{ip}" do before :each do @declaration = Puppet::Network::AuthStore::Declaration.new(:allow_ip,ip) end it "should match the specified IP" do @declaration.should be_match('www.testsite.org',ip) end it "should not match other IPs" do @declaration.should_not be_match('www.testsite.org','200.101.99.98') end end unless ip =~ /:.*\./ # Hybrid IPs aren't supported by ruby's ipaddr } [ "::2:3:4:5:6:7:8", ].each { |ip| describe "when the pattern is a valid IP such as #{ip}" do let(:declaration) do Puppet::Network::AuthStore::Declaration.new(:allow_ip,ip) end issue_7477 = !(IPAddr.new(ip) rescue false) it "should match the specified IP" do pending "resolution of ruby issue [7477](http://goo.gl/Bb1LU)", :if => issue_7477 declaration.should be_match('www.testsite.org',ip) end it "should not match other IPs" do pending "resolution of ruby issue [7477](http://goo.gl/Bb1LU)", :if => issue_7477 declaration.should_not be_match('www.testsite.org','200.101.99.98') end end } { 'spirit.mars.nasa.gov' => 'a PQDN', 'ratchet.2ndsiteinc.com' => 'a PQDN with digits', 'a.c.ru' => 'a PQDN with short segments', }.each {|pqdn,desc| describe "when the pattern is #{desc}" do before :each do @host = pqdn @declaration = Puppet::Network::AuthStore::Declaration.new(:allow,@host) end it "should match the specified PQDN" do @declaration.should be_match(@host,'200.101.99.98') end it "should not match a similar FQDN" do pending "FQDN consensus" @declaration.should_not be_match(@host+'.','200.101.99.98') end end } ['abc.12seps.edu.phisher.biz','www.google.com','slashdot.org'].each { |host| (1...(host.split('.').length)).each { |n| describe "when the pattern is #{"*."+host.split('.')[-n,n].join('.')}" do before :each do @pattern = "*."+host.split('.')[-n,n].join('.') @declaration = Puppet::Network::AuthStore::Declaration.new(:allow,@pattern) end it "should match #{host}" do @declaration.should be_match(host,'1.2.3.4') end it "should not match www.testsite.gov" do @declaration.should_not be_match('www.testsite.gov','200.101.99.98') end it "should not match hosts that differ in the first non-wildcard segment" do other = host.split('.') other[-n].succ! @declaration.should_not be_match(other.join('.'),'1.2.3.4') end end } } describe "when the pattern is a FQDN" do before :each do @host = 'spirit.mars.nasa.gov.' @declaration = Puppet::Network::AuthStore::Declaration.new(:allow,@host) end it "should match the specified FQDN" do pending "FQDN consensus" @declaration.should be_match(@host,'200.101.99.98') end it "should not match a similar PQDN" do @declaration.should_not be_match(@host[0..-2],'200.101.99.98') end end describe "when the pattern is an opaque string with a back reference" do before :each do @host = 'c216f41a-f902-4bfb-a222-850dd957bebb' @item = "/catalog/#{@host}" @pattern = %{^/catalog/([^/]+)$} @declaration = Puppet::Network::AuthStore::Declaration.new(:allow,'$1') end it "should match an IP with the appropriate interpolation" do @declaration.interpolate(@item.match(@pattern)).should be_match(@host,'10.0.0.5') end end describe "when the pattern is an opaque string with a back reference and the matched data contains dots" do before :each do @host = 'admin.mgmt.nym1' @item = "/catalog/#{@host}" @pattern = %{^/catalog/([^/]+)$} @declaration = Puppet::Network::AuthStore::Declaration.new(:allow,'$1') end it "should match a name with the appropriate interpolation" do @declaration.interpolate(@item.match(@pattern)).should be_match(@host,'10.0.0.5') end end describe "when the pattern is an opaque string with a back reference and the matched data contains dots with an initial prefix that looks like an IP address" do before :each do @host = '01.admin.mgmt.nym1' @item = "/catalog/#{@host}" @pattern = %{^/catalog/([^/]+)$} @declaration = Puppet::Network::AuthStore::Declaration.new(:allow,'$1') end it "should match a name with the appropriate interpolation" do @declaration.interpolate(@item.match(@pattern)).should be_match(@host,'10.0.0.5') end end describe "when comparing patterns" do before :each do @ip = Puppet::Network::AuthStore::Declaration.new(:allow,'127.0.0.1') @host_name = Puppet::Network::AuthStore::Declaration.new(:allow,'www.hard_knocks.edu') @opaque = Puppet::Network::AuthStore::Declaration.new(:allow,'hey_dude') end it "should consider ip addresses before host names" do - (@ip < @host_name).should be_true + (@ip < @host_name).should be_truthy end it "should consider ip addresses before opaque strings" do - (@ip < @opaque).should be_true + (@ip < @opaque).should be_truthy end it "should consider host_names before opaque strings" do - (@host_name < @opaque).should be_true + (@host_name < @opaque).should be_truthy end end end diff --git a/spec/unit/network/format_support_spec.rb b/spec/unit/network/format_support_spec.rb index 8e43ae135..486451f4b 100644 --- a/spec/unit/network/format_support_spec.rb +++ b/spec/unit/network/format_support_spec.rb @@ -1,199 +1,199 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/network/format_handler' require 'puppet/network/format_support' class FormatTester include Puppet::Network::FormatSupport end describe Puppet::Network::FormatHandler do before(:each) do @saved_formats = Puppet::Network::FormatHandler.instance_variable_get(:@formats).dup Puppet::Network::FormatHandler.instance_variable_set(:@formats, {}) end after(:each) do Puppet::Network::FormatHandler.instance_variable_set(:@formats, @saved_formats) end describe "when listing formats" do before(:each) do one = Puppet::Network::FormatHandler.create(:one, :weight => 1) one.stubs(:supported?).returns(true) two = Puppet::Network::FormatHandler.create(:two, :weight => 6) two.stubs(:supported?).returns(true) three = Puppet::Network::FormatHandler.create(:three, :weight => 2) three.stubs(:supported?).returns(true) four = Puppet::Network::FormatHandler.create(:four, :weight => 8) four.stubs(:supported?).returns(false) end it "should return all supported formats in decreasing order of weight" do FormatTester.supported_formats.should == [:two, :three, :one] end end it "should return the first format as the default format" do FormatTester.expects(:supported_formats).returns [:one, :two] FormatTester.default_format.should == :one end describe "with a preferred serialization format setting" do before do one = Puppet::Network::FormatHandler.create(:one, :weight => 1) one.stubs(:supported?).returns(true) two = Puppet::Network::FormatHandler.create(:two, :weight => 6) two.stubs(:supported?).returns(true) end describe "that is supported" do before do Puppet[:preferred_serialization_format] = :one end it "should return the preferred serialization format first" do FormatTester.supported_formats.should == [:one, :two] end end describe "that is not supported" do before do Puppet[:preferred_serialization_format] = :unsupported end it "should return the default format first" do FormatTester.supported_formats.should == [:two, :one] end it "should log a debug message" do Puppet.expects(:debug).with("Value of 'preferred_serialization_format' (unsupported) is invalid for FormatTester, using default (two)") Puppet.expects(:debug).with("FormatTester supports formats: two one") FormatTester.supported_formats end end end describe "when using formats" do let(:format) { Puppet::Network::FormatHandler.create(:my_format, :mime => "text/myformat") } it "should use the Format to determine whether a given format is supported" do format.expects(:supported?).with(FormatTester) FormatTester.support_format?(:my_format) end it "should call the format-specific converter when asked to convert from a given format" do format.expects(:intern).with(FormatTester, "mydata") FormatTester.convert_from(:my_format, "mydata") end it "should call the format-specific converter when asked to convert from a given format by mime-type" do format.expects(:intern).with(FormatTester, "mydata") FormatTester.convert_from("text/myformat", "mydata") end it "should call the format-specific converter when asked to convert from a given format by format instance" do format.expects(:intern).with(FormatTester, "mydata") FormatTester.convert_from(format, "mydata") end it "should raise a FormatError when an exception is encountered when converting from a format" do format.expects(:intern).with(FormatTester, "mydata").raises "foo" expect do FormatTester.convert_from(:my_format, "mydata") end.to raise_error( Puppet::Network::FormatHandler::FormatError, 'Could not intern from my_format: foo' ) end it "should be able to use a specific hook for converting into multiple instances" do format.expects(:intern_multiple).with(FormatTester, "mydata") FormatTester.convert_from_multiple(:my_format, "mydata") end it "should raise a FormatError when an exception is encountered when converting multiple items from a format" do format.expects(:intern_multiple).with(FormatTester, "mydata").raises "foo" expect do FormatTester.convert_from_multiple(:my_format, "mydata") end.to raise_error(Puppet::Network::FormatHandler::FormatError, 'Could not intern_multiple from my_format: foo') end it "should be able to use a specific hook for rendering multiple instances" do format.expects(:render_multiple).with("mydata") FormatTester.render_multiple(:my_format, "mydata") end it "should raise a FormatError when an exception is encountered when rendering multiple items into a format" do format.expects(:render_multiple).with("mydata").raises "foo" expect do FormatTester.render_multiple(:my_format, "mydata") end.to raise_error(Puppet::Network::FormatHandler::FormatError, 'Could not render_multiple to my_format: foo') end end describe "when an instance" do let(:format) { Puppet::Network::FormatHandler.create(:foo, :mime => "text/foo") } it "should list as supported a format that reports itself supported" do format.expects(:supported?).returns true - FormatTester.new.support_format?(:foo).should be_true + FormatTester.new.support_format?(:foo).should be_truthy end it "should raise a FormatError when a rendering error is encountered" do tester = FormatTester.new format.expects(:render).with(tester).raises "eh" expect do tester.render(:foo) end.to raise_error(Puppet::Network::FormatHandler::FormatError, 'Could not render to foo: eh') end it "should call the format-specific converter when asked to convert to a given format" do tester = FormatTester.new format.expects(:render).with(tester).returns "foo" tester.render(:foo).should == "foo" end it "should call the format-specific converter when asked to convert to a given format by mime-type" do tester = FormatTester.new format.expects(:render).with(tester).returns "foo" tester.render("text/foo").should == "foo" end it "should call the format converter when asked to convert to a given format instance" do tester = FormatTester.new format.expects(:render).with(tester).returns "foo" tester.render(format).should == "foo" end it "should render to the default format if no format is provided when rendering" do FormatTester.expects(:default_format).returns :foo tester = FormatTester.new format.expects(:render).with(tester) tester.render end it "should call the format-specific converter when asked for the mime-type of a given format" do tester = FormatTester.new format.expects(:mime).returns "text/foo" tester.mime(:foo).should == "text/foo" end it "should return the default format mime-type if no format is provided" do FormatTester.expects(:default_format).returns :foo tester = FormatTester.new format.expects(:mime).returns "text/foo" tester.mime.should == "text/foo" end end end diff --git a/spec/unit/network/http/factory_spec.rb b/spec/unit/network/http/factory_spec.rb index 58ab56fbd..d9dbbd38a 100755 --- a/spec/unit/network/http/factory_spec.rb +++ b/spec/unit/network/http/factory_spec.rb @@ -1,80 +1,80 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/network/http' describe Puppet::Network::HTTP::Factory do before :each do Puppet::SSL::Key.indirection.terminus_class = :memory Puppet::SSL::CertificateRequest.indirection.terminus_class = :memory end let(:site) { Puppet::Network::HTTP::Site.new('https', 'www.example.com', 443) } def create_connection(site) factory = Puppet::Network::HTTP::Factory.new factory.create_connection(site) end it 'creates a connection for the site' do conn = create_connection(site) - expect(conn.use_ssl?).to be_true + expect(conn.use_ssl?).to be_truthy expect(conn.address).to eq(site.host) expect(conn.port).to eq(site.port) end it 'creates a connection that has not yet been started' do conn = create_connection(site) expect(conn).to_not be_started end it 'creates a connection supporting at least HTTP 1.1' do conn = create_connection(site) - expect(any_of(conn.class.version_1_1?, conn.class.version_1_1?)).to be_true + expect(any_of(conn.class.version_1_1?, conn.class.version_1_1?)).to be_truthy end context "proxy settings" do let(:proxy_host) { 'myhost' } let(:proxy_port) { 432 } it "should not set a proxy if the value is 'none'" do Puppet[:http_proxy_host] = 'none' conn = create_connection(site) expect(conn.proxy_address).to be_nil end it 'sets proxy_address' do Puppet[:http_proxy_host] = proxy_host conn = create_connection(site) expect(conn.proxy_address).to eq(proxy_host) end it 'sets proxy address and port' do Puppet[:http_proxy_host] = proxy_host Puppet[:http_proxy_port] = proxy_port conn = create_connection(site) expect(conn.proxy_port).to eq(proxy_port) end context 'socket timeouts' do it 'sets open timeout' do Puppet[:http_connect_timeout] = "10s" conn = create_connection(site) expect(conn.open_timeout).to eq(10) end it 'sets read timeout' do Puppet[:http_read_timeout] = "2m" conn = create_connection(site) expect(conn.read_timeout).to eq(120) end end end end diff --git a/spec/unit/network/http/handler_spec.rb b/spec/unit/network/http/handler_spec.rb index 17c9e8958..ca1b1b788 100755 --- a/spec/unit/network/http/handler_spec.rb +++ b/spec/unit/network/http/handler_spec.rb @@ -1,179 +1,179 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/handler' require 'puppet/indirector_testing' require 'puppet/network/authorization' require 'puppet/network/http' describe Puppet::Network::HTTP::Handler do before :each do Puppet::IndirectorTesting.indirection.terminus_class = :memory end let(:indirection) { Puppet::IndirectorTesting.indirection } def a_request(method = "HEAD", path = "/production/#{indirection.name}/unknown") { :accept_header => "pson", :content_type_header => "text/pson", :method => method, :path => path, :params => {}, :client_cert => nil, :headers => {}, :body => nil } end let(:handler) { PuppetSpec::Handler.new() } describe "the HTTP Handler" do def respond(text) lambda { |req, res| res.respond_with(200, "text/plain", text) } end it "hands the request to the first route that matches the request path" do handler = PuppetSpec::Handler.new( Puppet::Network::HTTP::Route.path(%r{^/foo}).get(respond("skipped")), Puppet::Network::HTTP::Route.path(%r{^/vtest}).get(respond("used")), Puppet::Network::HTTP::Route.path(%r{^/vtest/foo}).get(respond("ignored"))) req = a_request("GET", "/vtest/foo") res = {} handler.process(req, res) expect(res[:body]).to eq("used") end it "raises an error if multiple routes with the same path regex are registered" do expect do handler = PuppetSpec::Handler.new( Puppet::Network::HTTP::Route.path(%r{^/foo}).get(respond("ignored")), Puppet::Network::HTTP::Route.path(%r{^/foo}).post(respond("also ignored"))) end.to raise_error(ArgumentError) end it "raises an HTTP not found error if no routes match" do handler = PuppetSpec::Handler.new req = a_request("GET", "/vtest/foo") res = {} handler.process(req, res) res_body = JSON(res[:body]) expect(res[:content_type_header]).to eq("application/json") expect(res_body["issue_kind"]).to eq("HANDLER_NOT_FOUND") expect(res_body["message"]).to eq("Not Found: No route for GET /vtest/foo") expect(res[:status]).to eq(404) end it "returns a structured error response with a stacktrace when the server encounters an internal error" do handler = PuppetSpec::Handler.new( Puppet::Network::HTTP::Route.path(/.*/).get(lambda { |_, _| raise StandardError.new("the sky is falling!")})) req = a_request("GET", "/vtest/foo") res = {} handler.process(req, res) res_body = JSON(res[:body]) expect(res[:content_type_header]).to eq("application/json") expect(res_body["issue_kind"]).to eq(Puppet::Network::HTTP::Issues::RUNTIME_ERROR.to_s) expect(res_body["message"]).to eq("Server Error: the sky is falling!") - expect(res_body["stacktrace"].is_a?(Array) && !res_body["stacktrace"].empty?).to be_true + expect(res_body["stacktrace"].is_a?(Array) && !res_body["stacktrace"].empty?).to be_truthy expect(res_body["stacktrace"][0]).to match("spec/unit/network/http/handler_spec.rb") expect(res[:status]).to eq(500) end end describe "when processing a request" do let(:response) do { :status => 200 } end before do handler.stubs(:check_authorization) handler.stubs(:warn_if_near_expiration) end it "should setup a profiler when the puppet-profiling header exists" do request = a_request request[:headers][Puppet::Network::HTTP::HEADER_ENABLE_PROFILING.downcase] = "true" p = PuppetSpec::HandlerProfiler.new Puppet::Util::Profiler.expects(:add_profiler).with { |profiler| profiler.is_a? Puppet::Util::Profiler::WallClock }.returns(p) Puppet::Util::Profiler.expects(:remove_profiler).with { |profiler| profiler == p } handler.process(request, response) end it "should not setup profiler when the profile parameter is missing" do request = a_request request[:params] = { } Puppet::Util::Profiler.expects(:add_profiler).never handler.process(request, response) end it "should raise an error if the request is formatted in an unknown format" do handler.stubs(:content_type_header).returns "unknown format" lambda { handler.request_format(request) }.should raise_error end it "should still find the correct format if content type contains charset information" do request = Puppet::Network::HTTP::Request.new({ 'content-type' => "text/plain; charset=UTF-8" }, {}, 'GET', '/', nil) request.format.should == "s" end # PUP-3272 # This used to be for YAML, and doing a to_yaml on an array. # The result with to_pson is something different, the result is a string # Which seems correct. Looks like this was some kind of nesting option "yaml inside yaml" ? # Removing the test # it "should deserialize PSON parameters" do # params = {'my_param' => [1,2,3].to_pson} # # decoded_params = handler.send(:decode_params, params) # # decoded_params.should == {:my_param => [1,2,3]} # end end describe "when resolving node" do it "should use a look-up from the ip address" do Resolv.expects(:getname).with("1.2.3.4").returns("host.domain.com") handler.resolve_node(:ip => "1.2.3.4") end it "should return the look-up result" do Resolv.stubs(:getname).with("1.2.3.4").returns("host.domain.com") handler.resolve_node(:ip => "1.2.3.4").should == "host.domain.com" end it "should return the ip address if resolving fails" do Resolv.stubs(:getname).with("1.2.3.4").raises(RuntimeError, "no such host") handler.resolve_node(:ip => "1.2.3.4").should == "1.2.3.4" end end end diff --git a/spec/unit/network/http/rack/rest_spec.rb b/spec/unit/network/http/rack/rest_spec.rb index f4125f170..9a51bf0a1 100755 --- a/spec/unit/network/http/rack/rest_spec.rb +++ b/spec/unit/network/http/rack/rest_spec.rb @@ -1,318 +1,318 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/network/http/rack' if Puppet.features.rack? require 'puppet/network/http/rack/rest' describe "Puppet::Network::HTTP::RackREST", :if => Puppet.features.rack? do it "should include the Puppet::Network::HTTP::Handler module" do Puppet::Network::HTTP::RackREST.ancestors.should be_include(Puppet::Network::HTTP::Handler) end describe "when serving a request" do before :all do @model_class = stub('indirected model class') Puppet::Indirector::Indirection.stubs(:model).with(:foo).returns(@model_class) end before :each do @response = Rack::Response.new @handler = Puppet::Network::HTTP::RackREST.new(:handler => :foo) end def mk_req(uri, opts = {}) env = Rack::MockRequest.env_for(uri, opts) Rack::Request.new(env) end let(:minimal_certificate) do key = OpenSSL::PKey::RSA.new(512) signer = Puppet::SSL::CertificateSigner.new cert = OpenSSL::X509::Certificate.new cert.version = 2 cert.serial = 0 cert.not_before = Time.now cert.not_after = Time.now + 3600 cert.public_key = key cert.subject = OpenSSL::X509::Name.parse("/CN=testing") signer.sign(cert, key) cert end describe "#headers" do it "should return the headers (parsed from env with prefix 'HTTP_')" do req = mk_req('/', {'HTTP_Accept' => 'myaccept', 'HTTP_X_Custom_Header' => 'mycustom', 'NOT_HTTP_foo' => 'not an http header'}) @handler.headers(req).should == {"accept" => 'myaccept', "x-custom-header" => 'mycustom', "content-type" => nil } end end describe "and using the HTTP Handler interface" do it "should return the CONTENT_TYPE parameter as the content type header" do req = mk_req('/', 'CONTENT_TYPE' => 'mycontent') @handler.headers(req)['content-type'].should == "mycontent" end it "should use the REQUEST_METHOD as the http method" do req = mk_req('/', :method => 'MYMETHOD') @handler.http_method(req).should == "MYMETHOD" end it "should return the request path as the path" do req = mk_req('/foo/bar') @handler.path(req).should == "/foo/bar" end it "should return the request body as the body" do req = mk_req('/foo/bar', :input => 'mybody') @handler.body(req).should == "mybody" end it "should return the an Puppet::SSL::Certificate instance as the client_cert" do req = mk_req('/foo/bar', 'SSL_CLIENT_CERT' => minimal_certificate.to_pem) expect(@handler.client_cert(req).content.to_pem).to eq(minimal_certificate.to_pem) end it "returns nil when SSL_CLIENT_CERT is empty" do req = mk_req('/foo/bar', 'SSL_CLIENT_CERT' => '') @handler.client_cert(req).should be_nil end it "should set the response's content-type header when setting the content type" do @header = mock 'header' @response.expects(:header).returns @header @header.expects(:[]=).with('Content-Type', "mytype") @handler.set_content_type(@response, "mytype") end it "should set the status and write the body when setting the response for a request" do @response.expects(:status=).with(400) @response.expects(:write).with("mybody") @handler.set_response(@response, "mybody", 400) end describe "when result is a File" do before :each do stat = stub 'stat', :size => 100 @file = stub 'file', :stat => stat, :path => "/tmp/path" @file.stubs(:is_a?).with(File).returns(true) end it "should set the Content-Length header as a string" do @response.expects(:[]=).with("Content-Length", '100') @handler.set_response(@response, @file, 200) end it "should return a RackFile adapter as body" do @response.expects(:body=).with { |val| val.is_a?(Puppet::Network::HTTP::RackREST::RackFile) } @handler.set_response(@response, @file, 200) end end it "should ensure the body has been read on success" do req = mk_req('/production/report/foo', :method => 'PUT') req.body.expects(:read).at_least_once Puppet::Transaction::Report.stubs(:save) @handler.process(req, @response) end it "should ensure the body has been partially read on failure" do req = mk_req('/production/report/foo') req.body.expects(:read).with(1) @handler.stubs(:headers).raises(StandardError) @handler.process(req, @response) end end describe "and determining the request parameters" do it "should include the HTTP request parameters, with the keys as symbols" do req = mk_req('/?foo=baz&bar=xyzzy') result = @handler.params(req) result[:foo].should == "baz" result[:bar].should == "xyzzy" end it "should return multi-values params as an array of the values" do req = mk_req('/?foo=baz&foo=xyzzy') result = @handler.params(req) result[:foo].should == ["baz", "xyzzy"] end it "should return parameters from the POST body" do req = mk_req("/", :method => 'POST', :input => 'foo=baz&bar=xyzzy') result = @handler.params(req) result[:foo].should == "baz" result[:bar].should == "xyzzy" end it "should not return multi-valued params in a POST body as an array of values" do req = mk_req("/", :method => 'POST', :input => 'foo=baz&foo=xyzzy') result = @handler.params(req) result[:foo].should be_one_of("baz", "xyzzy") end it "should CGI-decode the HTTP parameters" do encoding = CGI.escape("foo bar") req = mk_req("/?foo=#{encoding}") result = @handler.params(req) result[:foo].should == "foo bar" end it "should convert the string 'true' to the boolean" do req = mk_req("/?foo=true") result = @handler.params(req) - result[:foo].should be_true + result[:foo].should be_truthy end it "should convert the string 'false' to the boolean" do req = mk_req("/?foo=false") result = @handler.params(req) - result[:foo].should be_false + result[:foo].should be_falsey end it "should convert integer arguments to Integers" do req = mk_req("/?foo=15") result = @handler.params(req) result[:foo].should == 15 end it "should convert floating point arguments to Floats" do req = mk_req("/?foo=1.5") result = @handler.params(req) result[:foo].should == 1.5 end it "should treat YAML encoded parameters like it was any string" do escaping = CGI.escape(YAML.dump(%w{one two})) req = mk_req("/?foo=#{escaping}") @handler.params(req)[:foo].should == "---\n- one\n- two\n" end it "should not allow the client to set the node via the query string" do req = mk_req("/?node=foo") @handler.params(req)[:node].should be_nil end it "should not allow the client to set the IP address via the query string" do req = mk_req("/?ip=foo") @handler.params(req)[:ip].should be_nil end it "should pass the client's ip address to model find" do req = mk_req("/", 'REMOTE_ADDR' => 'ipaddress') @handler.params(req)[:ip].should == "ipaddress" end it "should set 'authenticated' to false if no certificate is present" do req = mk_req('/') - @handler.params(req)[:authenticated].should be_false + @handler.params(req)[:authenticated].should be_falsey end end describe "with pre-validated certificates" do it "should retrieve the hostname by finding the CN given in :ssl_client_header, in the format returned by Apache (RFC2253)" do Puppet[:ssl_client_header] = "myheader" req = mk_req('/', "myheader" => "O=Foo\\, Inc,CN=host.domain.com") @handler.params(req)[:node].should == "host.domain.com" end it "should retrieve the hostname by finding the CN given in :ssl_client_header, in the format returned by nginx" do Puppet[:ssl_client_header] = "myheader" req = mk_req('/', "myheader" => "/CN=host.domain.com") @handler.params(req)[:node].should == "host.domain.com" end it "should retrieve the hostname by finding the CN given in :ssl_client_header, ignoring other fields" do Puppet[:ssl_client_header] = "myheader" req = mk_req('/', "myheader" => 'ST=Denial,CN=host.domain.com,O=Domain\\, Inc.') @handler.params(req)[:node].should == "host.domain.com" end it "should use the :ssl_client_header to determine the parameter for checking whether the host certificate is valid" do Puppet[:ssl_client_header] = "certheader" Puppet[:ssl_client_verify_header] = "myheader" req = mk_req('/', "myheader" => "SUCCESS", "certheader" => "CN=host.domain.com") - @handler.params(req)[:authenticated].should be_true + @handler.params(req)[:authenticated].should be_truthy end it "should consider the host unauthenticated if the validity parameter does not contain 'SUCCESS'" do Puppet[:ssl_client_header] = "certheader" Puppet[:ssl_client_verify_header] = "myheader" req = mk_req('/', "myheader" => "whatever", "certheader" => "CN=host.domain.com") - @handler.params(req)[:authenticated].should be_false + @handler.params(req)[:authenticated].should be_falsey end it "should consider the host unauthenticated if no certificate information is present" do Puppet[:ssl_client_header] = "certheader" Puppet[:ssl_client_verify_header] = "myheader" req = mk_req('/', "myheader" => nil, "certheader" => "CN=host.domain.com") - @handler.params(req)[:authenticated].should be_false + @handler.params(req)[:authenticated].should be_falsey end it "should resolve the node name with an ip address look-up if no certificate is present" do Puppet[:ssl_client_header] = "myheader" req = mk_req('/', "myheader" => nil) @handler.expects(:resolve_node).returns("host.domain.com") @handler.params(req)[:node].should == "host.domain.com" end it "should resolve the node name with an ip address look-up if a certificate without a CN is present" do Puppet[:ssl_client_header] = "myheader" req = mk_req('/', "myheader" => "O=no CN") @handler.expects(:resolve_node).returns("host.domain.com") @handler.params(req)[:node].should == "host.domain.com" end it "should not allow authentication via the verify header if there is no CN available" do Puppet[:ssl_client_header] = "dn_header" Puppet[:ssl_client_verify_header] = "verify_header" req = mk_req('/', "dn_header" => "O=no CN", "verify_header" => 'SUCCESS') @handler.expects(:resolve_node).returns("host.domain.com") - @handler.params(req)[:authenticated].should be_false + @handler.params(req)[:authenticated].should be_falsey end end end end describe Puppet::Network::HTTP::RackREST::RackFile do before(:each) do stat = stub 'stat', :size => 100 @file = stub 'file', :stat => stat, :path => "/tmp/path" @rackfile = Puppet::Network::HTTP::RackREST::RackFile.new(@file) end it "should have an each method" do @rackfile.should be_respond_to(:each) end it "should yield file chunks by chunks" do @file.expects(:read).times(3).with(8192).returns("1", "2", nil) i = 1 @rackfile.each do |chunk| chunk.to_i.should == i i += 1 end end it "should have a close method" do @rackfile.should be_respond_to(:close) end it "should delegate close to File close" do @file.expects(:close) @rackfile.close end end diff --git a/spec/unit/network/http/route_spec.rb b/spec/unit/network/http/route_spec.rb index 04f8f8388..6f32c9bab 100644 --- a/spec/unit/network/http/route_spec.rb +++ b/spec/unit/network/http/route_spec.rb @@ -1,91 +1,91 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector_testing' require 'puppet/network/http' describe Puppet::Network::HTTP::Route do def request(method, path) Puppet::Network::HTTP::Request.from_hash({ :method => method, :path => path, :routing_path => path }) end def respond(text) lambda { |req, res| res.respond_with(200, "text/plain", text) } end let(:req) { request("GET", "/vtest/foo") } let(:res) { Puppet::Network::HTTP::MemoryResponse.new } describe "an HTTP Route" do it "can match a request" do route = Puppet::Network::HTTP::Route.path(%r{^/vtest}) - expect(route.matches?(req)).to be_true + expect(route.matches?(req)).to be_truthy end it "will raise a Method Not Allowed error when no handler for the request's method is given" do route = Puppet::Network::HTTP::Route.path(%r{^/vtest}).post(respond("ignored")) expect do route.process(req, res) end.to raise_error(Puppet::Network::HTTP::Error::HTTPMethodNotAllowedError) end it "can match any HTTP method" do route = Puppet::Network::HTTP::Route.path(%r{^/vtest/foo}).any(respond("used")) - expect(route.matches?(req)).to be_true + expect(route.matches?(req)).to be_truthy route.process(req, res) expect(res.body).to eq("used") end it "processes DELETE requests" do route = Puppet::Network::HTTP::Route.path(%r{^/vtest/foo}).delete(respond("used")) route.process(request("DELETE", "/vtest/foo"), res) expect(res.body).to eq("used") end it "does something when it doesn't know the verb" do route = Puppet::Network::HTTP::Route.path(%r{^/vtest/foo}) expect do route.process(request("UNKNOWN", "/vtest/foo"), res) end.to raise_error(Puppet::Network::HTTP::Error::HTTPMethodNotAllowedError, /UNKNOWN/) end it "calls the method handlers in turn" do call_count = 0 handler = lambda { |request, response| call_count += 1 } route = Puppet::Network::HTTP::Route.path(%r{^/vtest/foo}).get(handler, handler) route.process(req, res) expect(call_count).to eq(2) end it "stops calling handlers if one of them raises an error" do ignored_called = false ignored = lambda { |req, res| ignored_called = true } raise_error = lambda { |req, res| raise Puppet::Network::HTTP::Error::HTTPNotAuthorizedError, "go away" } route = Puppet::Network::HTTP::Route.path(%r{^/vtest/foo}).get(raise_error, ignored) expect do route.process(req, res) end.to raise_error(Puppet::Network::HTTP::Error::HTTPNotAuthorizedError) - expect(ignored_called).to be_false + expect(ignored_called).to be_falsey end it "chains to other routes after calling its handlers" do inner_route = Puppet::Network::HTTP::Route.path(%r{^/inner}).any(respond("inner")) unused_inner_route = Puppet::Network::HTTP::Route.path(%r{^/unused_inner}).any(respond("unused")) top_route = Puppet::Network::HTTP::Route.path(%r{^/vtest}).any(respond("top")).chain(unused_inner_route, inner_route) top_route.process(request("GET", "/vtest/inner"), res) expect(res.body).to eq("topinner") end end end diff --git a/spec/unit/network/http/session_spec.rb b/spec/unit/network/http/session_spec.rb index 4eba67d7d..86d904132 100755 --- a/spec/unit/network/http/session_spec.rb +++ b/spec/unit/network/http/session_spec.rb @@ -1,43 +1,43 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/network/http' describe Puppet::Network::HTTP::Session do let(:connection) { stub('connection') } def create_session(connection, expiration_time = nil) expiration_time ||= Time.now + 60 * 60 Puppet::Network::HTTP::Session.new(connection, expiration_time) end it 'provides access to its connection' do session = create_session(connection) session.connection.should == connection end it 'expires a connection whose expiration time is in the past' do now = Time.now past = now - 1 session = create_session(connection, past) - session.expired?(now).should be_true + session.expired?(now).should be_truthy end it 'expires a connection whose expiration time is now' do now = Time.now session = create_session(connection, now) - session.expired?(now).should be_true + session.expired?(now).should be_truthy end it 'does not expire a connection whose expiration time is in the future' do now = Time.now future = now + 1 session = create_session(connection, future) - session.expired?(now).should be_false + session.expired?(now).should be_falsey end end diff --git a/spec/unit/network/http/site_spec.rb b/spec/unit/network/http/site_spec.rb index 06fcbf83d..e740b5c9f 100755 --- a/spec/unit/network/http/site_spec.rb +++ b/spec/unit/network/http/site_spec.rb @@ -1,90 +1,90 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/network/http' describe Puppet::Network::HTTP::Site do let(:scheme) { 'https' } let(:host) { 'rubygems.org' } let(:port) { 443 } def create_site(scheme, host, port) Puppet::Network::HTTP::Site.new(scheme, host, port) end it 'accepts scheme, host, and port' do site = create_site(scheme, host, port) expect(site.scheme).to eq(scheme) expect(site.host).to eq(host) expect(site.port).to eq(port) end it 'generates an external URI string' do site = create_site(scheme, host, port) expect(site.addr).to eq("https://rubygems.org:443") end it 'considers sites to be different when the scheme is different' do https_site = create_site('https', host, port) http_site = create_site('http', host, port) expect(https_site).to_not eq(http_site) end it 'considers sites to be different when the host is different' do rubygems_site = create_site(scheme, 'rubygems.org', port) github_site = create_site(scheme, 'github.com', port) expect(rubygems_site).to_not eq(github_site) end it 'considers sites to be different when the port is different' do site_443 = create_site(scheme, host, 443) site_80 = create_site(scheme, host, 80) expect(site_443).to_not eq(site_80) end it 'compares values when determining equality' do site = create_site(scheme, host, port) sites = {} sites[site] = site another_site = create_site(scheme, host, port) - expect(sites.include?(another_site)).to be_true + expect(sites.include?(another_site)).to be_truthy end it 'computes the same hash code for equivalent objects' do site = create_site(scheme, host, port) same_site = create_site(scheme, host, port) expect(site.hash).to eq(same_site.hash) end it 'uses ssl with https' do site = create_site('https', host, port) expect(site).to be_use_ssl end it 'does not use ssl with http' do site = create_site('http', host, port) expect(site).to_not be_use_ssl end it 'moves to a new URI location' do site = create_site('http', 'host1', 80) uri = URI.parse('https://host2:443/some/where/else') new_site = site.move_to(uri) expect(new_site.scheme).to eq('https') expect(new_site.host).to eq('host2') expect(new_site.port).to eq(443) end end diff --git a/spec/unit/network/http/webrick/rest_spec.rb b/spec/unit/network/http/webrick/rest_spec.rb index 72e810b01..d113d1fd3 100755 --- a/spec/unit/network/http/webrick/rest_spec.rb +++ b/spec/unit/network/http/webrick/rest_spec.rb @@ -1,231 +1,231 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/network/http' require 'webrick' require 'puppet/network/http/webrick/rest' describe Puppet::Network::HTTP::WEBrickREST do it "should include the Puppet::Network::HTTP::Handler module" do Puppet::Network::HTTP::WEBrickREST.ancestors.should be_include(Puppet::Network::HTTP::Handler) end describe "when receiving a request" do before do @request = stub('webrick http request', :query => {}, :query_string => 'environment=production', :peeraddr => %w{eh boo host ip}, :request_method => 'GET', :client_cert => nil) @response = mock('webrick http response') @model_class = stub('indirected model class') @webrick = stub('webrick http server', :mount => true, :[] => {}) Puppet::Indirector::Indirection.stubs(:model).with(:foo).returns(@model_class) @handler = Puppet::Network::HTTP::WEBrickREST.new(@webrick) end it "should delegate its :service method to its :process method" do @handler.expects(:process).with(@request, @response).returns "stuff" @handler.service(@request, @response).should == "stuff" end describe "#headers" do let(:fake_request) { {"Foo" => "bar", "BAZ" => "bam" } } it "should iterate over the request object using #each" do fake_request.expects(:each) @handler.headers(fake_request) end it "should return a hash with downcased header names" do result = @handler.headers(fake_request) result.should == fake_request.inject({}) { |m,(k,v)| m[k.downcase] = v; m } end end describe "when using the Handler interface" do it "should use the request method as the http method" do @request.expects(:request_method).returns "FOO" @handler.http_method(@request).should == "FOO" end it "should return the request path as the path" do @request.expects(:path).returns "/foo/bar" @handler.path(@request).should == "/foo/bar" end it "should return the request body as the body" do @request.stubs(:request_method).returns "POST" @request.expects(:body).returns "my body" @handler.body(@request).should == "my body" end it "should set the response's 'content-type' header when setting the content type" do @response.expects(:[]=).with("content-type", "text/html") @handler.set_content_type(@response, "text/html") end it "should set the status and body on the response when setting the response for a successful query" do @response.expects(:status=).with 200 @response.expects(:body=).with "mybody" @handler.set_response(@response, "mybody", 200) end it "serves a file" do stat = stub 'stat', :size => 100 @file = stub 'file', :stat => stat, :path => "/tmp/path" @file.stubs(:is_a?).with(File).returns(true) @response.expects(:[]=).with('content-length', 100) @response.expects(:status=).with 200 @response.expects(:body=).with @file @handler.set_response(@response, @file, 200) end it "should set the status and message on the response when setting the response for a failed query" do @response.expects(:status=).with 400 @response.expects(:body=).with "mybody" @handler.set_response(@response, "mybody", 400) end end describe "and determining the request parameters" do def query_of(options) request = Puppet::Indirector::Request.new(:myind, :find, "my key", nil, options) WEBrick::HTTPUtils.parse_query(request.query_string.sub(/^\?/, '')) end def a_request_querying(query_data) @request.expects(:query).returns(query_of(query_data)) @request end def certificate_with_subject(subj) cert = OpenSSL::X509::Certificate.new cert.subject = OpenSSL::X509::Name.parse(subj) cert end it "has no parameters when there is no query string" do only_server_side_information = [:authenticated, :ip, :node] @request.stubs(:query).returns(nil) result = @handler.params(@request) result.keys.sort.should == only_server_side_information end it "should prefer duplicate params from the body over the query string" do @request.stubs(:request_method).returns "PUT" @request.stubs(:query).returns(WEBrick::HTTPUtils.parse_query("foo=bar&environment=posted_env")) @handler.params(@request)[:environment].should == "posted_env" end it "should include the HTTP request parameters, with the keys as symbols" do request = a_request_querying("foo" => "baz", "bar" => "xyzzy") result = @handler.params(request) result[:foo].should == "baz" result[:bar].should == "xyzzy" end it "should handle parameters with no value" do request = a_request_querying('foo' => "") result = @handler.params(request) result[:foo].should == "" end it "should convert the string 'true' to the boolean" do request = a_request_querying('foo' => "true") result = @handler.params(request) result[:foo].should == true end it "should convert the string 'false' to the boolean" do request = a_request_querying('foo' => "false") result = @handler.params(request) result[:foo].should == false end it "should reconstruct arrays" do request = a_request_querying('foo' => ["a", "b", "c"]) result = @handler.params(request) result[:foo].should == ["a", "b", "c"] end it "should convert values inside arrays into primitive types" do request = a_request_querying('foo' => ["true", "false", "1", "1.2"]) result = @handler.params(request) result[:foo].should == [true, false, 1, 1.2] end it "should treat YAML-load values that are YAML-encoded as any other String" do request = a_request_querying('foo' => YAML.dump(%w{one two})) @handler.params(request)[:foo].should == "---\n- one\n- two\n" end it "should not allow clients to set the node via the request parameters" do request = a_request_querying("node" => "foo") @handler.stubs(:resolve_node) @handler.params(request)[:node].should be_nil end it "should not allow clients to set the IP via the request parameters" do request = a_request_querying("ip" => "foo") @handler.params(request)[:ip].should_not == "foo" end it "should pass the client's ip address to model find" do @request.stubs(:peeraddr).returns(%w{noidea dunno hostname ipaddress}) @handler.params(@request)[:ip].should == "ipaddress" end it "should set 'authenticated' to true if a certificate is present" do cert = stub 'cert', :subject => [%w{CN host.domain.com}] @request.stubs(:client_cert).returns cert - @handler.params(@request)[:authenticated].should be_true + @handler.params(@request)[:authenticated].should be_truthy end it "should set 'authenticated' to false if no certificate is present" do @request.stubs(:client_cert).returns nil - @handler.params(@request)[:authenticated].should be_false + @handler.params(@request)[:authenticated].should be_falsey end it "should pass the client's certificate name to model method if a certificate is present" do @request.stubs(:client_cert).returns(certificate_with_subject("/CN=host.domain.com")) @handler.params(@request)[:node].should == "host.domain.com" end it "should resolve the node name with an ip address look-up if no certificate is present" do @request.stubs(:client_cert).returns nil @handler.expects(:resolve_node).returns(:resolved_node) @handler.params(@request)[:node].should == :resolved_node end it "should resolve the node name with an ip address look-up if CN parsing fails" do @request.stubs(:client_cert).returns(certificate_with_subject("/C=company")) @handler.expects(:resolve_node).returns(:resolved_node) @handler.params(@request)[:node].should == :resolved_node end end end end diff --git a/spec/unit/network/http/webrick_spec.rb b/spec/unit/network/http/webrick_spec.rb index 15821fb93..b9a588924 100755 --- a/spec/unit/network/http/webrick_spec.rb +++ b/spec/unit/network/http/webrick_spec.rb @@ -1,279 +1,279 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/network/http' require 'puppet/network/http/webrick' describe Puppet::Network::HTTP::WEBrick, "after initializing" do it "should not be listening" do Puppet::Network::HTTP::WEBrick.new.should_not be_listening end end describe Puppet::Network::HTTP::WEBrick do include PuppetSpec::Files let(:address) { '127.0.0.1' } let(:port) { 31337 } let(:server) do s = Puppet::Network::HTTP::WEBrick.new s.stubs(:setup_logger).returns(Hash.new) s.stubs(:setup_ssl).returns(Hash.new) s end let(:mock_ssl_context) do stub('ssl_context', :ciphers= => nil) end let(:mock_webrick) do stub('webrick', :[] => {}, :listeners => [], :status => :Running, :mount => nil, :start => nil, :shutdown => nil, :ssl_context => mock_ssl_context) end before :each do WEBrick::HTTPServer.stubs(:new).returns(mock_webrick) end describe "when turning on listening" do it "should fail if already listening" do server.listen(address, port) expect { server.listen(address, port) }.to raise_error(RuntimeError, /server is already listening/) end it "should tell webrick to listen on the specified address and port" do WEBrick::HTTPServer.expects(:new).with( has_entries(:Port => 31337, :BindAddress => "127.0.0.1") ).returns(mock_webrick) server.listen(address, port) end it "should not perform reverse lookups" do WEBrick::HTTPServer.expects(:new).with( has_entry(:DoNotReverseLookup => true) ).returns(mock_webrick) BasicSocket.expects(:do_not_reverse_lookup=).with(true) server.listen(address, port) end it "should configure a logger for webrick" do server.expects(:setup_logger).returns(:Logger => :mylogger) WEBrick::HTTPServer.expects(:new).with {|args| args[:Logger] == :mylogger }.returns(mock_webrick) server.listen(address, port) end it "should configure SSL for webrick" do server.expects(:setup_ssl).returns(:Ssl => :testing, :Other => :yay) WEBrick::HTTPServer.expects(:new).with {|args| args[:Ssl] == :testing and args[:Other] == :yay }.returns(mock_webrick) server.listen(address, port) end it "should be listening" do server.listen(address, port) server.should be_listening end describe "when the REST protocol is requested" do it "should register the REST handler at /" do # We don't care about the options here. mock_webrick.expects(:mount).with("/", Puppet::Network::HTTP::WEBrickREST, anything) server.listen(address, port) end end end describe "when turning off listening" do it "should fail unless listening" do expect { server.unlisten }.to raise_error(RuntimeError, /server is not listening/) end it "should order webrick server to stop" do mock_webrick.expects(:shutdown) server.listen(address, port) server.unlisten end it "should no longer be listening" do server.listen(address, port) server.unlisten server.should_not be_listening end end describe "when configuring an http logger" do let(:server) { Puppet::Network::HTTP::WEBrick.new } before :each do Puppet.settings.stubs(:use) @filehandle = stub 'handle', :fcntl => nil, :sync= => nil File.stubs(:open).returns @filehandle end it "should use the settings for :main, :ssl, and :application" do Puppet.settings.expects(:use).with(:main, :ssl, :application) server.setup_logger end it "should use the masterhttplog" do log = make_absolute("/master/log") Puppet[:masterhttplog] = log File.expects(:open).with(log, "a+").returns @filehandle server.setup_logger end describe "and creating the logging filehandle" do it "should set the close-on-exec flag if supported" do if defined? Fcntl::FD_CLOEXEC @filehandle.expects(:fcntl).with(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) else @filehandle.expects(:fcntl).never end server.setup_logger end it "should sync the filehandle" do @filehandle.expects(:sync=).with(true) server.setup_logger end end it "should create a new WEBrick::Log instance with the open filehandle" do WEBrick::Log.expects(:new).with(@filehandle) server.setup_logger end it "should set debugging if the current loglevel is :debug" do Puppet::Util::Log.expects(:level).returns :debug WEBrick::Log.expects(:new).with { |handle, debug| debug == WEBrick::Log::DEBUG } server.setup_logger end it "should return the logger as the main log" do logger = mock 'logger' WEBrick::Log.expects(:new).returns logger server.setup_logger[:Logger].should == logger end it "should return the logger as the access log using both the Common and Referer log format" do logger = mock 'logger' WEBrick::Log.expects(:new).returns logger server.setup_logger[:AccessLog].should == [ [logger, WEBrick::AccessLog::COMMON_LOG_FORMAT], [logger, WEBrick::AccessLog::REFERER_LOG_FORMAT] ] end end describe "when configuring ssl" do let(:server) { Puppet::Network::HTTP::WEBrick.new } let(:localcacert) { make_absolute("/ca/crt") } let(:ssl_server_ca_auth) { make_absolute("/ca/ssl_server_auth_file") } let(:key) { stub 'key', :content => "mykey" } let(:cert) { stub 'cert', :content => "mycert" } let(:host) { stub 'host', :key => key, :certificate => cert, :name => "yay", :ssl_store => "mystore" } before :each do Puppet::SSL::Certificate.indirection.stubs(:find).with('ca').returns cert Puppet::SSL::Host.stubs(:localhost).returns host end it "should use the key from the localhost SSL::Host instance" do Puppet::SSL::Host.expects(:localhost).returns host host.expects(:key).returns key server.setup_ssl[:SSLPrivateKey].should == "mykey" end it "should configure the certificate" do server.setup_ssl[:SSLCertificate].should == "mycert" end it "should fail if no CA certificate can be found" do Puppet::SSL::Certificate.indirection.stubs(:find).with('ca').returns nil expect { server.setup_ssl }.to raise_error(Puppet::Error, /Could not find CA certificate/) end it "should specify the path to the CA certificate" do Puppet.settings[:hostcrl] = 'false' Puppet.settings[:localcacert] = localcacert server.setup_ssl[:SSLCACertificateFile].should == localcacert end it "should specify the path to the CA certificate" do Puppet.settings[:hostcrl] = 'false' Puppet.settings[:localcacert] = localcacert Puppet.settings[:ssl_server_ca_auth] = ssl_server_ca_auth server.setup_ssl[:SSLCACertificateFile].should == ssl_server_ca_auth end it "should start ssl immediately" do - server.setup_ssl[:SSLStartImmediately].should be_true + server.setup_ssl[:SSLStartImmediately].should be_truthy end it "should enable ssl" do - server.setup_ssl[:SSLEnable].should be_true + server.setup_ssl[:SSLEnable].should be_truthy end it "should reject SSLv2" do options = server.setup_ssl[:SSLOptions] expect(options & OpenSSL::SSL::OP_NO_SSLv2).to eq(OpenSSL::SSL::OP_NO_SSLv2) end it "should reject SSLv3" do options = server.setup_ssl[:SSLOptions] expect(options & OpenSSL::SSL::OP_NO_SSLv3).to eq(OpenSSL::SSL::OP_NO_SSLv3) end it "should configure the verification method as 'OpenSSL::SSL::VERIFY_PEER'" do server.setup_ssl[:SSLVerifyClient].should == OpenSSL::SSL::VERIFY_PEER end it "should add an x509 store" do host.expects(:ssl_store).returns "mystore" server.setup_ssl[:SSLCertificateStore].should == "mystore" end it "should set the certificate name to 'nil'" do server.setup_ssl[:SSLCertName].should be_nil end it "specifies the allowable ciphers" do mock_ssl_context.expects(:ciphers=).with(server.class::CIPHERS) server.create_server('localhost', '8888') end end end diff --git a/spec/unit/network/rights_spec.rb b/spec/unit/network/rights_spec.rb index 08e8f775a..5c5097d7a 100755 --- a/spec/unit/network/rights_spec.rb +++ b/spec/unit/network/rights_spec.rb @@ -1,440 +1,440 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/network/rights' describe Puppet::Network::Rights do before do @right = Puppet::Network::Rights.new end describe "when validating a :head request" do [:find, :save].each do |allowed_method| it "should allow the request if only #{allowed_method} is allowed" do rights = Puppet::Network::Rights.new right = rights.newright("/") right.allow("*") right.restrict_method(allowed_method) right.restrict_authenticated(:any) rights.is_request_forbidden_and_why?(:head, "/indirection_name/key", {}).should == nil end end it "should disallow the request if neither :find nor :save is allowed" do rights = Puppet::Network::Rights.new why_forbidden = rights.is_request_forbidden_and_why?(:head, "/indirection_name/key", {}) why_forbidden.should be_instance_of(Puppet::Network::AuthorizationError) why_forbidden.to_s.should == "Forbidden request: access to /indirection_name/key [find]" end end it "should throw an error if type can't be determined" do lambda { @right.newright("name") }.should raise_error end describe "when creating new path ACLs" do it "should not throw an error if the ACL already exists" do @right.newright("/name") lambda { @right.newright("/name")}.should_not raise_error end it "should throw an error if the acl uri path is not absolute" do lambda { @right.newright("name")}.should raise_error end it "should create a new ACL with the correct path" do @right.newright("/name") @right["/name"].should_not be_nil end it "should create an ACL of type Puppet::Network::AuthStore" do @right.newright("/name") @right["/name"].should be_a_kind_of(Puppet::Network::AuthStore) end end describe "when creating new regex ACLs" do it "should not throw an error if the ACL already exists" do @right.newright("~ .rb$") lambda { @right.newright("~ .rb$")}.should_not raise_error end it "should create a new ACL with the correct regex" do @right.newright("~ .rb$") @right.include?(".rb$").should_not be_nil end it "should be able to lookup the regex" do @right.newright("~ .rb$") @right[".rb$"].should_not be_nil end it "should be able to lookup the regex by its full name" do @right.newright("~ .rb$") @right["~ .rb$"].should_not be_nil end it "should create an ACL of type Puppet::Network::AuthStore" do @right.newright("~ .rb$").should be_a_kind_of(Puppet::Network::AuthStore) end end describe "when checking ACLs existence" do it "should return false if there are no matching rights" do - @right.include?("name").should be_false + @right.include?("name").should be_falsey end it "should return true if a path right exists" do @right.newright("/name") - @right.include?("/name").should be_true + @right.include?("/name").should be_truthy end it "should return false if no matching path rights exist" do @right.newright("/name") - @right.include?("/differentname").should be_false + @right.include?("/differentname").should be_falsey end it "should return true if a regex right exists" do @right.newright("~ .rb$") - @right.include?(".rb$").should be_true + @right.include?(".rb$").should be_truthy end it "should return false if no matching path rights exist" do @right.newright("~ .rb$") - @right.include?(".pp$").should be_false + @right.include?(".pp$").should be_falsey end end describe "when checking if right is allowed" do before :each do @right.stubs(:right).returns(nil) @pathacl = stub 'pathacl', :"<=>" => 1, :line => 0, :file => 'dummy' Puppet::Network::Rights::Right.stubs(:new).returns(@pathacl) end it "should delegate to is_forbidden_and_why?" do @right.expects(:is_forbidden_and_why?).with("namespace", :node => "host.domain.com", :ip => "127.0.0.1").returns(nil) @right.allowed?("namespace", "host.domain.com", "127.0.0.1") end it "should return true if is_forbidden_and_why? returns nil" do @right.stubs(:is_forbidden_and_why?).returns(nil) - @right.allowed?("namespace", :args).should be_true + @right.allowed?("namespace", :args).should be_truthy end it "should return false if is_forbidden_and_why? returns an AuthorizationError" do @right.stubs(:is_forbidden_and_why?).returns(Puppet::Network::AuthorizationError.new("forbidden")) - @right.allowed?("namespace", :args1, :args2).should be_false + @right.allowed?("namespace", :args1, :args2).should be_falsey end it "should pass the match? return to allowed?" do @right.newright("/path/to/there") @pathacl.expects(:match?).returns(:match) @pathacl.expects(:allowed?).with { |node,ip,h| h[:match] == :match }.returns(true) @right.is_forbidden_and_why?("/path/to/there", {}).should == nil end describe "with path acls" do before :each do @long_acl = stub 'longpathacl', :name => "/path/to/there", :line => 0, :file => 'dummy' Puppet::Network::Rights::Right.stubs(:new).with("/path/to/there", 0, nil).returns(@long_acl) @short_acl = stub 'shortpathacl', :name => "/path/to", :line => 0, :file => 'dummy' Puppet::Network::Rights::Right.stubs(:new).with("/path/to", 0, nil).returns(@short_acl) @long_acl.stubs(:"<=>").with(@short_acl).returns(0) @short_acl.stubs(:"<=>").with(@long_acl).returns(0) end it "should select the first match" do @right.newright("/path/to", 0) @right.newright("/path/to/there", 0) @long_acl.stubs(:match?).returns(true) @short_acl.stubs(:match?).returns(true) @short_acl.expects(:allowed?).returns(true) @long_acl.expects(:allowed?).never @right.is_forbidden_and_why?("/path/to/there/and/there", {}).should == nil end it "should select the first match that doesn't return :dunno" do @right.newright("/path/to/there", 0, nil) @right.newright("/path/to", 0, nil) @long_acl.stubs(:match?).returns(true) @short_acl.stubs(:match?).returns(true) @long_acl.expects(:allowed?).returns(:dunno) @short_acl.expects(:allowed?).returns(true) @right.is_forbidden_and_why?("/path/to/there/and/there", {}).should == nil end it "should not select an ACL that doesn't match" do @right.newright("/path/to/there", 0) @right.newright("/path/to", 0) @long_acl.stubs(:match?).returns(false) @short_acl.stubs(:match?).returns(true) @long_acl.expects(:allowed?).never @short_acl.expects(:allowed?).returns(true) @right.is_forbidden_and_why?("/path/to/there/and/there", {}).should == nil end it "should not raise an AuthorizationError if allowed" do @right.newright("/path/to/there", 0) @long_acl.stubs(:match?).returns(true) @long_acl.stubs(:allowed?).returns(true) @right.is_forbidden_and_why?("/path/to/there/and/there", {}).should == nil end it "should raise an AuthorizationError if the match is denied" do @right.newright("/path/to/there", 0, nil) @long_acl.stubs(:match?).returns(true) @long_acl.stubs(:allowed?).returns(false) @right.is_forbidden_and_why?("/path/to/there", {}).should be_instance_of(Puppet::Network::AuthorizationError) end it "should raise an AuthorizationError if no path match" do @right.is_forbidden_and_why?("/nomatch", {}).should be_instance_of(Puppet::Network::AuthorizationError) end end describe "with regex acls" do before :each do @regex_acl1 = stub 'regex_acl1', :name => "/files/(.*)/myfile", :line => 0, :file => 'dummy' Puppet::Network::Rights::Right.stubs(:new).with("~ /files/(.*)/myfile", 0, nil).returns(@regex_acl1) @regex_acl2 = stub 'regex_acl2', :name => "/files/(.*)/myfile/", :line => 0, :file => 'dummy' Puppet::Network::Rights::Right.stubs(:new).with("~ /files/(.*)/myfile/", 0, nil).returns(@regex_acl2) @regex_acl1.stubs(:"<=>").with(@regex_acl2).returns(0) @regex_acl2.stubs(:"<=>").with(@regex_acl1).returns(0) end it "should select the first match" do @right.newright("~ /files/(.*)/myfile", 0) @right.newright("~ /files/(.*)/myfile/", 0) @regex_acl1.stubs(:match?).returns(true) @regex_acl2.stubs(:match?).returns(true) @regex_acl1.expects(:allowed?).returns(true) @regex_acl2.expects(:allowed?).never @right.is_forbidden_and_why?("/files/repository/myfile/other", {}).should == nil end it "should select the first match that doesn't return :dunno" do @right.newright("~ /files/(.*)/myfile", 0) @right.newright("~ /files/(.*)/myfile/", 0) @regex_acl1.stubs(:match?).returns(true) @regex_acl2.stubs(:match?).returns(true) @regex_acl1.expects(:allowed?).returns(:dunno) @regex_acl2.expects(:allowed?).returns(true) @right.is_forbidden_and_why?("/files/repository/myfile/other", {}).should == nil end it "should not select an ACL that doesn't match" do @right.newright("~ /files/(.*)/myfile", 0) @right.newright("~ /files/(.*)/myfile/", 0) @regex_acl1.stubs(:match?).returns(false) @regex_acl2.stubs(:match?).returns(true) @regex_acl1.expects(:allowed?).never @regex_acl2.expects(:allowed?).returns(true) @right.is_forbidden_and_why?("/files/repository/myfile/other", {}).should == nil end it "should not raise an AuthorizationError if allowed" do @right.newright("~ /files/(.*)/myfile", 0) @regex_acl1.stubs(:match?).returns(true) @regex_acl1.stubs(:allowed?).returns(true) @right.is_forbidden_and_why?("/files/repository/myfile/other", {}).should == nil end it "should raise an error if no regex acl match" do @right.is_forbidden_and_why?("/path", {}).should be_instance_of(Puppet::Network::AuthorizationError) end it "should raise an AuthorizedError on deny" do @right.is_forbidden_and_why?("/path", {}).should be_instance_of(Puppet::Network::AuthorizationError) end end end describe Puppet::Network::Rights::Right do before :each do @acl = Puppet::Network::Rights::Right.new("/path",0, nil) end describe "with path" do it "should match up to its path length" do @acl.match?("/path/that/works").should_not be_nil end it "should match up to its path length" do @acl.match?("/paththatalsoworks").should_not be_nil end it "should return nil if no match" do @acl.match?("/notpath").should be_nil end end describe "with regex" do before :each do @acl = Puppet::Network::Rights::Right.new("~ .rb$",0, nil) end it "should match as a regex" do @acl.match?("this should work.rb").should_not be_nil end it "should return nil if no match" do @acl.match?("do not match").should be_nil end end it "should allow all rest methods by default" do @acl.methods.should == Puppet::Network::Rights::Right::ALL end it "should allow only authenticated request by default" do - @acl.authentication.should be_true + @acl.authentication.should be_truthy end it "should allow modification of the methods filters" do @acl.restrict_method(:save) @acl.methods.should == [:save] end it "should stack methods filters" do @acl.restrict_method(:save) @acl.restrict_method(:destroy) @acl.methods.should == [:save, :destroy] end it "should raise an error if the method is already filtered" do @acl.restrict_method(:save) lambda { @acl.restrict_method(:save) }.should raise_error end it "should allow setting an environment filters" do env = Puppet::Node::Environment.create(:acltest, []) Puppet.override(:environments => Puppet::Environments::Static.new(env)) do @acl.restrict_environment(:acltest) @acl.environment.should == [env] end end ["on", "yes", "true", true].each do |auth| it "should allow filtering on authenticated requests with '#{auth}'" do @acl.restrict_authenticated(auth) - @acl.authentication.should be_true + @acl.authentication.should be_truthy end end ["off", "no", "false", false, "all", "any", :all, :any].each do |auth| it "should allow filtering on authenticated or unauthenticated requests with '#{auth}'" do @acl.restrict_authenticated(auth) - @acl.authentication.should be_false + @acl.authentication.should be_falsey end end describe "when checking right authorization" do it "should return :dunno if this right is not restricted to the given method" do @acl.restrict_method(:destroy) @acl.allowed?("me","127.0.0.1", { :method => :save } ).should == :dunno end it "should return true if this right is restricted to the given method" do @acl.restrict_method(:save) @acl.allow("me") @acl.allowed?("me","127.0.0.1", { :method => :save, :authenticated => true }).should eq true end it "should return :dunno if this right is not restricted to the given environment" do prod = Puppet::Node::Environment.create(:production, []) dev = Puppet::Node::Environment.create(:development, []) Puppet.override(:environments => Puppet::Environments::Static.new(prod, dev)) do @acl.restrict_environment(:production) @acl.allowed?("me","127.0.0.1", { :method => :save, :environment => dev }).should == :dunno end end it "returns true if the request is permitted for this environment" do @acl.allow("me") prod = Puppet::Node::Environment.create(:production, []) Puppet.override(:environments => Puppet::Environments::Static.new(prod)) do @acl.restrict_environment(:production) expect(@acl.allowed?("me", "127.0.0.1", { :method => :save, :authenticated => true, :environment => prod })).to eq true end end it "should return :dunno if this right is not restricted to the given request authentication state" do @acl.restrict_authenticated(true) @acl.allowed?("me","127.0.0.1", { :method => :save, :authenticated => false }).should == :dunno end it "returns true if this right is restricted to the given request authentication state" do @acl.restrict_authenticated(false) @acl.allow("me") @acl.allowed?("me","127.0.0.1", {:method => :save, :authenticated => false }).should eq true end it "should interpolate allow/deny patterns with the given match" do @acl.expects(:interpolate).with(:match) @acl.allowed?("me","127.0.0.1", { :method => :save, :match => :match, :authenticated => true }) end it "should reset interpolation after the match" do @acl.expects(:reset_interpolation) @acl.allowed?("me","127.0.0.1", { :method => :save, :match => :match, :authenticated => true }) end end end end diff --git a/spec/unit/node/environment_spec.rb b/spec/unit/node/environment_spec.rb index 74238542f..535b94a48 100755 --- a/spec/unit/node/environment_spec.rb +++ b/spec/unit/node/environment_spec.rb @@ -1,483 +1,483 @@ #! /usr/bin/env ruby require 'spec_helper' require 'tmpdir' require 'puppet/node/environment' require 'puppet/util/execution' require 'puppet_spec/modules' require 'puppet/parser/parser_factory' describe Puppet::Node::Environment do let(:env) { Puppet::Node::Environment.create("testing", []) } include PuppetSpec::Files context 'the environment' do it "converts an environment to string when converting to YAML" do env.to_yaml.should match(/--- testing/) end describe ".create" do it "creates equivalent environments whether specifying name as a symbol or a string" do expect(Puppet::Node::Environment.create(:one, [])).to eq(Puppet::Node::Environment.create("one", [])) end it "interns name" do expect(Puppet::Node::Environment.create("one", []).name).to equal(:one) end it "does not produce environment singletons" do expect(Puppet::Node::Environment.create("one", [])).to_not equal(Puppet::Node::Environment.create("one", [])) end end it "returns its name when converted to a string" do expect(env.to_s).to eq("testing") end it "has an inspect method for debugging" do e = Puppet::Node::Environment.create(:test, ['/modules/path', '/other/modules'], '/manifests/path') expect("a #{e} env").to eq("a test env") expect(e.inspect).to match(%r{}) end describe "equality" do it "works as a hash key" do base = Puppet::Node::Environment.create(:first, ["modules"], "manifests") same = Puppet::Node::Environment.create(:first, ["modules"], "manifests") different = Puppet::Node::Environment.create(:first, ["different"], "manifests") hash = {} hash[base] = "base env" hash[same] = "same env" hash[different] = "different env" expect(hash[base]).to eq("same env") expect(hash[different]).to eq("different env") expect(hash).to have(2).item end it "is equal when name, modules, and manifests are the same" do base = Puppet::Node::Environment.create(:base, ["modules"], "manifests") different_name = Puppet::Node::Environment.create(:different, base.full_modulepath, base.manifest) expect(base).to_not eq("not an environment") expect(base).to eq(base) expect(base.hash).to eq(base.hash) expect(base.override_with(:modulepath => ["different"])).to_not eq(base) expect(base.override_with(:modulepath => ["different"]).hash).to_not eq(base.hash) expect(base.override_with(:manifest => "different")).to_not eq(base) expect(base.override_with(:manifest => "different").hash).to_not eq(base.hash) expect(different_name).to_not eq(base) expect(different_name.hash).to_not eq(base.hash) end end describe "overriding an existing environment" do let(:original_path) { [tmpdir('original')] } let(:new_path) { [tmpdir('new')] } let(:environment) { Puppet::Node::Environment.create(:overridden, original_path, 'orig.pp', '/config/script') } it "overrides modulepath" do overridden = environment.override_with(:modulepath => new_path) expect(overridden).to_not be_equal(environment) expect(overridden.name).to eq(:overridden) expect(overridden.manifest).to eq(File.expand_path('orig.pp')) expect(overridden.modulepath).to eq(new_path) expect(overridden.config_version).to eq('/config/script') end it "overrides manifest" do overridden = environment.override_with(:manifest => 'new.pp') expect(overridden).to_not be_equal(environment) expect(overridden.name).to eq(:overridden) expect(overridden.manifest).to eq(File.expand_path('new.pp')) expect(overridden.modulepath).to eq(original_path) expect(overridden.config_version).to eq('/config/script') end it "overrides config_version" do overridden = environment.override_with(:config_version => '/new/script') expect(overridden).to_not be_equal(environment) expect(overridden.name).to eq(:overridden) expect(overridden.manifest).to eq(File.expand_path('orig.pp')) expect(overridden.modulepath).to eq(original_path) expect(overridden.config_version).to eq('/new/script') end end describe "when managing known resource types" do before do env.stubs(:perform_initial_import).returns(Puppet::Parser::AST::Hostclass.new('')) end it "creates a resource type collection if none exists" do expect(env.known_resource_types).to be_kind_of(Puppet::Resource::TypeCollection) end it "memoizes resource type collection" do expect(env.known_resource_types).to equal(env.known_resource_types) end it "performs the initial import when creating a new collection" do env.expects(:perform_initial_import).returns(Puppet::Parser::AST::Hostclass.new('')) env.known_resource_types end it "generates a new TypeCollection if the current one requires reparsing" do old_type_collection = env.known_resource_types old_type_collection.stubs(:parse_failed?).returns true env.check_for_reparse new_type_collection = env.known_resource_types expect(new_type_collection).to be_a Puppet::Resource::TypeCollection expect(new_type_collection).to_not equal(old_type_collection) end end it "validates the modulepath directories" do real_file = tmpdir('moduledir') path = ['/one', '/two', real_file] env = Puppet::Node::Environment.create(:test, path) expect(env.modulepath).to eq([real_file]) end it "prefixes the value of the 'PUPPETLIB' environment variable to the module path if present" do first_puppetlib = tmpdir('puppetlib1') second_puppetlib = tmpdir('puppetlib2') first_moduledir = tmpdir('moduledir1') second_moduledir = tmpdir('moduledir2') Puppet::Util.withenv("PUPPETLIB" => [first_puppetlib, second_puppetlib].join(File::PATH_SEPARATOR)) do env = Puppet::Node::Environment.create(:testing, [first_moduledir, second_moduledir]) expect(env.modulepath).to eq([first_puppetlib, second_puppetlib, first_moduledir, second_moduledir]) end end describe "validating manifest settings" do before(:each) do Puppet[:default_manifest] = "/default/manifests/site.pp" end it "has no validation errors when disable_per_environment_manifest is false" do expect(Puppet::Node::Environment.create(:directory, [], '/some/non/default/manifest.pp').validation_errors).to be_empty end context "when disable_per_environment_manifest is true" do let(:config) { mock('config') } let(:global_modulepath) { ["/global/modulepath"] } let(:envconf) { Puppet::Settings::EnvironmentConf.new("/some/direnv", config, global_modulepath) } before(:each) do Puppet[:disable_per_environment_manifest] = true end def assert_manifest_conflict(expectation, envconf_manifest_value) config.expects(:setting).with(:manifest).returns( mock('setting', :value => envconf_manifest_value) ) environment = Puppet::Node::Environment.create(:directory, [], '/default/manifests/site.pp') loader = Puppet::Environments::Static.new(environment) loader.stubs(:get_conf).returns(envconf) Puppet.override(:environments => loader) do if expectation expect(environment.validation_errors).to have_matching_element(/The 'disable_per_environment_manifest' setting is true.*and the.*environment.*conflicts/) else expect(environment.validation_errors).to be_empty end end end it "has conflicting_manifest_settings when environment.conf manifest was set" do assert_manifest_conflict(true, '/some/envconf/manifest/site.pp') end it "does not have conflicting_manifest_settings when environment.conf manifest is empty" do assert_manifest_conflict(false, '') end it "does not have conflicting_manifest_settings when environment.conf manifest is nil" do assert_manifest_conflict(false, nil) end it "does not have conflicting_manifest_settings when environment.conf manifest is an exact, uninterpolated match of default_manifest" do assert_manifest_conflict(false, '/default/manifests/site.pp') end end end describe "when modeling a specific environment" do let(:first_modulepath) { tmpdir('firstmodules') } let(:second_modulepath) { tmpdir('secondmodules') } let(:env) { Puppet::Node::Environment.create(:modules_test, [first_modulepath, second_modulepath]) } let(:module_options) { { :environment => env, :metadata => { :author => 'puppetlabs', }, } } describe "module data" do describe ".module" do it "returns an individual module that exists in its module path" do one = PuppetSpec::Modules.create('one', first_modulepath, module_options) expect(env.module('one')).to eq(one) end it "returns nil if asked for a module that does not exist in its path" do expect(env.module("doesnotexist")).to be_nil end end describe "#modules_by_path" do it "returns an empty list if there are no modules" do expect(env.modules_by_path).to eq({ first_modulepath => [], second_modulepath => [] }) end it "includes modules even if they exist in multiple dirs in the modulepath" do one = PuppetSpec::Modules.create('one', first_modulepath, module_options) two = PuppetSpec::Modules.create('two', second_modulepath, module_options) expect(env.modules_by_path).to eq({ first_modulepath => [one], second_modulepath => [two], }) end it "ignores modules with invalid names" do PuppetSpec::Modules.generate_files('foo', first_modulepath) PuppetSpec::Modules.generate_files('foo2', first_modulepath) PuppetSpec::Modules.generate_files('foo-bar', first_modulepath) PuppetSpec::Modules.generate_files('foo_bar', first_modulepath) PuppetSpec::Modules.generate_files('foo=bar', first_modulepath) PuppetSpec::Modules.generate_files('foo bar', first_modulepath) PuppetSpec::Modules.generate_files('foo.bar', first_modulepath) PuppetSpec::Modules.generate_files('-foo', first_modulepath) PuppetSpec::Modules.generate_files('foo-', first_modulepath) PuppetSpec::Modules.generate_files('foo--bar', first_modulepath) expect(env.modules_by_path[first_modulepath].collect{|mod| mod.name}.sort).to eq(%w{foo foo-bar foo2 foo_bar}) end end describe "#module_requirements" do it "returns a list of what modules depend on other modules" do PuppetSpec::Modules.create( 'foo', first_modulepath, :metadata => { :author => 'puppetlabs', :dependencies => [{ 'name' => 'puppetlabs/bar', "version_requirement" => ">= 1.0.0" }] } ) PuppetSpec::Modules.create( 'bar', second_modulepath, :metadata => { :author => 'puppetlabs', :dependencies => [{ 'name' => 'puppetlabs/foo', "version_requirement" => "<= 2.0.0" }] } ) PuppetSpec::Modules.create( 'baz', first_modulepath, :metadata => { :author => 'puppetlabs', :dependencies => [{ 'name' => 'puppetlabs-bar', "version_requirement" => "3.0.0" }] } ) PuppetSpec::Modules.create( 'alpha', first_modulepath, :metadata => { :author => 'puppetlabs', :dependencies => [{ 'name' => 'puppetlabs/bar', "version_requirement" => "~3.0.0" }] } ) expect(env.module_requirements).to eq({ 'puppetlabs/alpha' => [], 'puppetlabs/foo' => [ { "name" => "puppetlabs/bar", "version" => "9.9.9", "version_requirement" => "<= 2.0.0" } ], 'puppetlabs/bar' => [ { "name" => "puppetlabs/alpha", "version" => "9.9.9", "version_requirement" => "~3.0.0" }, { "name" => "puppetlabs/baz", "version" => "9.9.9", "version_requirement" => "3.0.0" }, { "name" => "puppetlabs/foo", "version" => "9.9.9", "version_requirement" => ">= 1.0.0" } ], 'puppetlabs/baz' => [] }) end end describe ".module_by_forge_name" do it "finds modules by forge_name" do mod = PuppetSpec::Modules.create( 'baz', first_modulepath, module_options ) expect(env.module_by_forge_name('puppetlabs/baz')).to eq(mod) end it "does not find modules with same name by the wrong author" do mod = PuppetSpec::Modules.create( 'baz', first_modulepath, :metadata => {:author => 'sneakylabs'}, :environment => env ) expect(env.module_by_forge_name('puppetlabs/baz')).to eq(nil) end it "returns nil when the module can't be found" do expect(env.module_by_forge_name('ima/nothere')).to be_nil end end describe ".modules" do it "returns an empty list if there are no modules" do expect(env.modules).to eq([]) end it "returns a module named for every directory in each module path" do %w{foo bar}.each do |mod_name| PuppetSpec::Modules.generate_files(mod_name, first_modulepath) end %w{bee baz}.each do |mod_name| PuppetSpec::Modules.generate_files(mod_name, second_modulepath) end expect(env.modules.collect{|mod| mod.name}.sort).to eq(%w{foo bar bee baz}.sort) end it "removes duplicates" do PuppetSpec::Modules.generate_files('foo', first_modulepath) PuppetSpec::Modules.generate_files('foo', second_modulepath) expect(env.modules.collect{|mod| mod.name}.sort).to eq(%w{foo}) end it "ignores modules with invalid names" do PuppetSpec::Modules.generate_files('foo', first_modulepath) PuppetSpec::Modules.generate_files('foo2', first_modulepath) PuppetSpec::Modules.generate_files('foo-bar', first_modulepath) PuppetSpec::Modules.generate_files('foo_bar', first_modulepath) PuppetSpec::Modules.generate_files('foo=bar', first_modulepath) PuppetSpec::Modules.generate_files('foo bar', first_modulepath) expect(env.modules.collect{|mod| mod.name}.sort).to eq(%w{foo foo-bar foo2 foo_bar}) end it "creates modules with the correct environment" do PuppetSpec::Modules.generate_files('foo', first_modulepath) env.modules.each do |mod| expect(mod.environment).to eq(env) end end it "logs an exception if a module contains invalid metadata" do PuppetSpec::Modules.generate_files( 'foo', first_modulepath, :metadata => { :author => 'puppetlabs' # missing source, version, etc } ) Puppet.expects(:log_exception).with(is_a(Puppet::Module::MissingMetadata)) env.modules end end end end describe "when performing initial import" do it "loads from Puppet[:code]" do Puppet[:code] = "define foo {}" krt = env.known_resource_types expect(krt.find_definition('foo')).to be_kind_of(Puppet::Resource::Type) end it "parses from the the environment's manifests if Puppet[:code] is not set" do filename = tmpfile('a_manifest.pp') File.open(filename, 'w') do |f| f.puts("define from_manifest {}") end env = Puppet::Node::Environment.create(:testing, [], filename) krt = env.known_resource_types expect(krt.find_definition('from_manifest')).to be_kind_of(Puppet::Resource::Type) end it "prefers Puppet[:code] over manifest files" do Puppet[:code] = "define from_code_setting {}" filename = tmpfile('a_manifest.pp') File.open(filename, 'w') do |f| f.puts("define from_manifest {}") end env = Puppet::Node::Environment.create(:testing, [], filename) krt = env.known_resource_types expect(krt.find_definition('from_code_setting')).to be_kind_of(Puppet::Resource::Type) end it "initial import proceeds even if manifest file does not exist on disk" do filename = tmpfile('a_manifest.pp') env = Puppet::Node::Environment.create(:testing, [], filename) expect(env.known_resource_types).to be_kind_of(Puppet::Resource::TypeCollection) end it "returns an empty TypeCollection if neither code nor manifests is present" do expect(env.known_resource_types).to be_kind_of(Puppet::Resource::TypeCollection) end it "fails helpfully if there is an error importing" do Puppet[:code] = "oops {" expect do env.known_resource_types end.to raise_error(Puppet::Error, /Could not parse for environment #{env.name}/) end it "should mark the type collection as needing a reparse when there is an error parsing" do Puppet[:code] = "oops {" expect do env.known_resource_types end.to raise_error(Puppet::Error, /Syntax error at .../) - expect(env.known_resource_types.parse_failed?).to be_true + expect(env.known_resource_types.parse_failed?).to be_truthy end end end end diff --git a/spec/unit/parser/compiler_spec.rb b/spec/unit/parser/compiler_spec.rb index 40ad20130..d34f33379 100755 --- a/spec/unit/parser/compiler_spec.rb +++ b/spec/unit/parser/compiler_spec.rb @@ -1,911 +1,911 @@ require 'spec_helper' require 'puppet_spec/compiler' require 'matchers/resource' class CompilerTestResource attr_accessor :builtin, :virtual, :evaluated, :type, :title def initialize(type, title) @type = type @title = title end def [](attr) return nil if attr == :stage :main end def ref "#{type.to_s.capitalize}[#{title}]" end def evaluated? @evaluated end def builtin_type? @builtin end def virtual? @virtual end def class? false end def stage? false end def evaluate end def file "/fake/file/goes/here" end def line "42" end end describe Puppet::Parser::Compiler do include PuppetSpec::Files include Matchers::Resource def resource(type, title) Puppet::Parser::Resource.new(type, title, :scope => @scope) end let(:environment) { Puppet::Node::Environment.create(:testing, []) } before :each do # Push me faster, I wanna go back in time! (Specifically, freeze time # across the test since we have a bunch of version == timestamp code # hidden away in the implementation and we keep losing the race.) # --daniel 2011-04-21 now = Time.now Time.stubs(:now).returns(now) @node = Puppet::Node.new("testnode", :facts => Puppet::Node::Facts.new("facts", {}), :environment => environment) @known_resource_types = environment.known_resource_types @compiler = Puppet::Parser::Compiler.new(@node) @scope = Puppet::Parser::Scope.new(@compiler, :source => stub('source')) @scope_resource = Puppet::Parser::Resource.new(:file, "/my/file", :scope => @scope) @scope.resource = @scope_resource end it "should fail intelligently when a class-level compile fails" do Puppet::Parser::Compiler.expects(:new).raises ArgumentError lambda { Puppet::Parser::Compiler.compile(@node) }.should raise_error(Puppet::Error) end it "should use the node's environment as its environment" do @compiler.environment.should equal(@node.environment) end it "fails if the node's environment has validation errors" do conflicted_environment = Puppet::Node::Environment.create(:testing, [], '/some/environment.conf/manifest.pp') conflicted_environment.stubs(:validation_errors).returns(['bad environment']) @node.environment = conflicted_environment expect { Puppet::Parser::Compiler.compile(@node) }.to raise_error(Puppet::Error, /Compilation has been halted because.*bad environment/) end it "should include the resource type collection helper" do Puppet::Parser::Compiler.ancestors.should be_include(Puppet::Resource::TypeCollectionHelper) end it "should be able to return a class list containing all added classes" do @compiler.add_class "" @compiler.add_class "one" @compiler.add_class "two" @compiler.classlist.sort.should == %w{one two}.sort end describe "when initializing" do it "should set its node attribute" do @compiler.node.should equal(@node) end it "should detect when ast nodes are absent" do - @compiler.ast_nodes?.should be_false + @compiler.ast_nodes?.should be_falsey end it "should detect when ast nodes are present" do @known_resource_types.expects(:nodes?).returns true - @compiler.ast_nodes?.should be_true + @compiler.ast_nodes?.should be_truthy end it "should copy the known_resource_types version to the catalog" do @compiler.catalog.version.should == @known_resource_types.version end it "should copy any node classes into the class list" do node = Puppet::Node.new("mynode") node.classes = %w{foo bar} compiler = Puppet::Parser::Compiler.new(node) compiler.classlist.should =~ ['foo', 'bar'] end it "should transform node class hashes into a class list" do node = Puppet::Node.new("mynode") node.classes = {'foo'=>{'one'=>'p1'}, 'bar'=>{'two'=>'p2'}} compiler = Puppet::Parser::Compiler.new(node) compiler.classlist.should =~ ['foo', 'bar'] end it "should add a 'main' stage to the catalog" do @compiler.catalog.resource(:stage, :main).should be_instance_of(Puppet::Parser::Resource) end end describe "when managing scopes" do it "should create a top scope" do @compiler.topscope.should be_instance_of(Puppet::Parser::Scope) end it "should be able to create new scopes" do @compiler.newscope(@compiler.topscope).should be_instance_of(Puppet::Parser::Scope) end it "should set the parent scope of the new scope to be the passed-in parent" do scope = mock 'scope' newscope = @compiler.newscope(scope) newscope.parent.should equal(scope) end it "should set the parent scope of the new scope to its topscope if the parent passed in is nil" do scope = mock 'scope' newscope = @compiler.newscope(nil) newscope.parent.should equal(@compiler.topscope) end end describe "when compiling" do def compile_methods [:set_node_parameters, :evaluate_main, :evaluate_ast_node, :evaluate_node_classes, :evaluate_generators, :fail_on_unevaluated, :finish, :store, :extract, :evaluate_relationships] end # Stub all of the main compile methods except the ones we're specifically interested in. def compile_stub(*except) (compile_methods - except).each { |m| @compiler.stubs(m) } end it "should set node parameters as variables in the top scope" do params = {"a" => "b", "c" => "d"} @node.stubs(:parameters).returns(params) compile_stub(:set_node_parameters) @compiler.compile @compiler.topscope['a'].should == "b" @compiler.topscope['c'].should == "d" end it "should set the client and server versions on the catalog" do params = {"clientversion" => "2", "serverversion" => "3"} @node.stubs(:parameters).returns(params) compile_stub(:set_node_parameters) @compiler.compile @compiler.catalog.client_version.should == "2" @compiler.catalog.server_version.should == "3" end it "should evaluate the main class if it exists" do compile_stub(:evaluate_main) main_class = @known_resource_types.add Puppet::Resource::Type.new(:hostclass, "") main_class.expects(:evaluate_code).with { |r| r.is_a?(Puppet::Parser::Resource) } @compiler.topscope.expects(:source=).with(main_class) @compiler.compile end it "should create a new, empty 'main' if no main class exists" do compile_stub(:evaluate_main) @compiler.compile @known_resource_types.find_hostclass("").should be_instance_of(Puppet::Resource::Type) end it "should add an edge between the main stage and main class" do @compiler.compile (stage = @compiler.catalog.resource(:stage, "main")).should be_instance_of(Puppet::Parser::Resource) (klass = @compiler.catalog.resource(:class, "")).should be_instance_of(Puppet::Parser::Resource) - @compiler.catalog.edge?(stage, klass).should be_true + @compiler.catalog.edge?(stage, klass).should be_truthy end it "should evaluate all added collections" do colls = [] # And when the collections fail to evaluate. colls << mock("coll1-false") colls << mock("coll2-false") colls.each { |c| c.expects(:evaluate).returns(false) } @compiler.add_collection(colls[0]) @compiler.add_collection(colls[1]) compile_stub(:evaluate_generators) @compiler.compile end it "should ignore builtin resources" do resource = resource(:file, "testing") @compiler.add_resource(@scope, resource) resource.expects(:evaluate).never @compiler.compile end it "should evaluate unevaluated resources" do resource = CompilerTestResource.new(:file, "testing") @compiler.add_resource(@scope, resource) # We have to now mark the resource as evaluated resource.expects(:evaluate).with { |*whatever| resource.evaluated = true } @compiler.compile end it "should not evaluate already-evaluated resources" do resource = resource(:file, "testing") resource.stubs(:evaluated?).returns true @compiler.add_resource(@scope, resource) resource.expects(:evaluate).never @compiler.compile end it "should evaluate unevaluated resources created by evaluating other resources" do resource = CompilerTestResource.new(:file, "testing") @compiler.add_resource(@scope, resource) resource2 = CompilerTestResource.new(:file, "other") # We have to now mark the resource as evaluated resource.expects(:evaluate).with { |*whatever| resource.evaluated = true; @compiler.add_resource(@scope, resource2) } resource2.expects(:evaluate).with { |*whatever| resource2.evaluated = true } @compiler.compile end describe "when finishing" do before do @compiler.send(:evaluate_main) @catalog = @compiler.catalog end def add_resource(name, parent = nil) resource = Puppet::Parser::Resource.new "file", name, :scope => @scope @compiler.add_resource(@scope, resource) @catalog.add_edge(parent, resource) if parent resource end it "should call finish() on all resources" do # Add a resource that does respond to :finish resource = Puppet::Parser::Resource.new "file", "finish", :scope => @scope resource.expects(:finish) @compiler.add_resource(@scope, resource) # And one that does not dnf_resource = stub_everything "dnf", :ref => "File[dnf]", :type => "file" @compiler.add_resource(@scope, dnf_resource) @compiler.send(:finish) end it "should call finish() in add_resource order" do resources = sequence('resources') resource1 = add_resource("finish1") resource1.expects(:finish).in_sequence(resources) resource2 = add_resource("finish2") resource2.expects(:finish).in_sequence(resources) @compiler.send(:finish) end it "should add each container's metaparams to its contained resources" do main = @catalog.resource(:class, :main) main[:noop] = true resource1 = add_resource("meh", main) @compiler.send(:finish) - resource1[:noop].should be_true + resource1[:noop].should be_truthy end it "should add metaparams recursively" do main = @catalog.resource(:class, :main) main[:noop] = true resource1 = add_resource("meh", main) resource2 = add_resource("foo", resource1) @compiler.send(:finish) - resource2[:noop].should be_true + resource2[:noop].should be_truthy end it "should prefer metaparams from immediate parents" do main = @catalog.resource(:class, :main) main[:noop] = true resource1 = add_resource("meh", main) resource2 = add_resource("foo", resource1) resource1[:noop] = false @compiler.send(:finish) - resource2[:noop].should be_false + resource2[:noop].should be_falsey end it "should merge tags downward" do main = @catalog.resource(:class, :main) main.tag("one") resource1 = add_resource("meh", main) resource1.tag "two" resource2 = add_resource("foo", resource1) @compiler.send(:finish) resource2.tags.should be_include("one") resource2.tags.should be_include("two") end it "should work if only middle resources have metaparams set" do main = @catalog.resource(:class, :main) resource1 = add_resource("meh", main) resource1[:noop] = true resource2 = add_resource("foo", resource1) @compiler.send(:finish) - resource2[:noop].should be_true + resource2[:noop].should be_truthy end end it "should return added resources in add order" do resource1 = resource(:file, "yay") @compiler.add_resource(@scope, resource1) resource2 = resource(:file, "youpi") @compiler.add_resource(@scope, resource2) @compiler.resources.should == [resource1, resource2] end it "should add resources that do not conflict with existing resources" do resource = resource(:file, "yay") @compiler.add_resource(@scope, resource) @compiler.catalog.should be_vertex(resource) end it "should fail to add resources that conflict with existing resources" do path = make_absolute("/foo") file1 = resource(:file, path) file2 = resource(:file, path) @compiler.add_resource(@scope, file1) lambda { @compiler.add_resource(@scope, file2) }.should raise_error(Puppet::Resource::Catalog::DuplicateResourceError) end it "should add an edge from the scope resource to the added resource" do resource = resource(:file, "yay") @compiler.add_resource(@scope, resource) @compiler.catalog.should be_edge(@scope.resource, resource) end it "should not add non-class resources that don't specify a stage to the 'main' stage" do main = @compiler.catalog.resource(:stage, :main) resource = resource(:file, "foo") @compiler.add_resource(@scope, resource) @compiler.catalog.should_not be_edge(main, resource) end it "should not add any parent-edges to stages" do stage = resource(:stage, "other") @compiler.add_resource(@scope, stage) @scope.resource = resource(:class, "foo") - @compiler.catalog.edge?(@scope.resource, stage).should be_false + @compiler.catalog.edge?(@scope.resource, stage).should be_falsey end it "should not attempt to add stages to other stages" do other_stage = resource(:stage, "other") second_stage = resource(:stage, "second") @compiler.add_resource(@scope, other_stage) @compiler.add_resource(@scope, second_stage) second_stage[:stage] = "other" - @compiler.catalog.edge?(other_stage, second_stage).should be_false + @compiler.catalog.edge?(other_stage, second_stage).should be_falsey end it "should have a method for looking up resources" do resource = resource(:yay, "foo") @compiler.add_resource(@scope, resource) @compiler.findresource("Yay[foo]").should equal(resource) end it "should be able to look resources up by type and title" do resource = resource(:yay, "foo") @compiler.add_resource(@scope, resource) @compiler.findresource("Yay", "foo").should equal(resource) end it "should not evaluate virtual defined resources" do resource = resource(:file, "testing") resource.virtual = true @compiler.add_resource(@scope, resource) resource.expects(:evaluate).never @compiler.compile end end describe "when evaluating collections" do it "should evaluate each collection" do 2.times { |i| coll = mock 'coll%s' % i @compiler.add_collection(coll) # This is the hard part -- we have to emulate the fact that # collections delete themselves if they are done evaluating. coll.expects(:evaluate).with do @compiler.delete_collection(coll) end } @compiler.compile end it "should not fail when there are unevaluated resource collections that do not refer to specific resources" do coll = stub 'coll', :evaluate => false coll.expects(:unresolved_resources).returns(nil) @compiler.add_collection(coll) lambda { @compiler.compile }.should_not raise_error end it "should fail when there are unevaluated resource collections that refer to a specific resource" do coll = stub 'coll', :evaluate => false coll.expects(:unresolved_resources).returns(:something) @compiler.add_collection(coll) lambda { @compiler.compile }.should raise_error(Puppet::ParseError, 'Failed to realize virtual resources something') end it "should fail when there are unevaluated resource collections that refer to multiple specific resources" do coll = stub 'coll', :evaluate => false coll.expects(:unresolved_resources).returns([:one, :two]) @compiler.add_collection(coll) lambda { @compiler.compile }.should raise_error(Puppet::ParseError, 'Failed to realize virtual resources one, two') end end describe "when evaluating relationships" do it "should evaluate each relationship with its catalog" do dep = stub 'dep' dep.expects(:evaluate).with(@compiler.catalog) @compiler.add_relationship dep @compiler.evaluate_relationships end end describe "when told to evaluate missing classes" do it "should fail if there's no source listed for the scope" do scope = stub 'scope', :source => nil proc { @compiler.evaluate_classes(%w{one two}, scope) }.should raise_error(Puppet::DevError) end it "should raise an error if a class is not found" do @scope.expects(:find_hostclass).with("notfound").returns(nil) lambda{ @compiler.evaluate_classes(%w{notfound}, @scope) }.should raise_error(Puppet::Error, /Could not find class/) end it "should raise an error when it can't find class" do klasses = {'foo'=>nil} @node.classes = klasses @compiler.topscope.expects(:find_hostclass).with('foo').returns(nil) lambda{ @compiler.compile }.should raise_error(Puppet::Error, /Could not find class foo for testnode/) end end describe "when evaluating found classes" do before do Puppet.settings[:data_binding_terminus] = "none" @class = stub 'class', :name => "my::class" @scope.stubs(:find_hostclass).with("myclass").returns(@class) @resource = stub 'resource', :ref => "Class[myclass]", :type => "file" end around do |example| Puppet.override( :environments => Puppet::Environments::Static.new(environment), :description => "Static loader for specs" ) do example.run end end it "should evaluate each class" do @compiler.catalog.stubs(:tag) @class.expects(:ensure_in_catalog).with(@scope) @scope.stubs(:class_scope).with(@class) @compiler.evaluate_classes(%w{myclass}, @scope) end describe "and the classes are specified as a hash with parameters" do before do @node.classes = {} @ast_obj = Puppet::Parser::AST::Leaf.new(:value => 'foo') end # Define the given class with default parameters def define_class(name, parameters) @node.classes[name] = parameters klass = Puppet::Resource::Type.new(:hostclass, name, :arguments => {'p1' => @ast_obj, 'p2' => @ast_obj}) @compiler.topscope.known_resource_types.add klass end def compile @catalog = @compiler.compile end it "should record which classes are evaluated" do classes = {'foo'=>{}, 'bar::foo'=>{}, 'bar'=>{}} classes.each { |c, params| define_class(c, params) } compile() classes.each { |name, p| @catalog.classes.should include(name) } end it "should provide default values for parameters that have no values specified" do define_class('foo', {}) compile() @catalog.resource(:class, 'foo')['p1'].should == "foo" end it "should use any provided values" do define_class('foo', {'p1' => 'real_value'}) compile() @catalog.resource(:class, 'foo')['p1'].should == "real_value" end it "should support providing some but not all values" do define_class('foo', {'p1' => 'real_value'}) compile() @catalog.resource(:class, 'Foo')['p1'].should == "real_value" @catalog.resource(:class, 'Foo')['p2'].should == "foo" end it "should ensure each node class is in catalog and has appropriate tags" do klasses = ['bar::foo'] @node.classes = klasses ast_obj = Puppet::Parser::AST::Leaf.new(:value => 'foo') klasses.each do |name| klass = Puppet::Resource::Type.new(:hostclass, name, :arguments => {'p1' => ast_obj, 'p2' => ast_obj}) @compiler.topscope.known_resource_types.add klass end catalog = @compiler.compile r2 = catalog.resources.detect {|r| r.title == 'Bar::Foo' } r2.tags.should == Puppet::Util::TagSet.new(['bar::foo', 'class', 'bar', 'foo']) end end it "should fail if required parameters are missing" do klass = {'foo'=>{'a'=>'one'}} @node.classes = klass klass = Puppet::Resource::Type.new(:hostclass, 'foo', :arguments => {'a' => nil, 'b' => nil}) @compiler.topscope.known_resource_types.add klass lambda { @compiler.compile }.should raise_error(Puppet::ParseError, "Must pass b to Class[Foo]") end it "should fail if invalid parameters are passed" do klass = {'foo'=>{'3'=>'one'}} @node.classes = klass klass = Puppet::Resource::Type.new(:hostclass, 'foo', :arguments => {}) @compiler.topscope.known_resource_types.add klass lambda { @compiler.compile }.should raise_error(Puppet::ParseError, "Invalid parameter: '3' on Class[Foo]") end it "should ensure class is in catalog without params" do @node.classes = klasses = {'foo'=>nil} foo = Puppet::Resource::Type.new(:hostclass, 'foo') @compiler.topscope.known_resource_types.add foo catalog = @compiler.compile catalog.classes.should include 'foo' end it "should not evaluate the resources created for found classes unless asked" do @compiler.catalog.stubs(:tag) @resource.expects(:evaluate).never @class.expects(:ensure_in_catalog).returns(@resource) @scope.stubs(:class_scope).with(@class) @compiler.evaluate_classes(%w{myclass}, @scope) end it "should immediately evaluate the resources created for found classes when asked" do @compiler.catalog.stubs(:tag) @resource.expects(:evaluate) @class.expects(:ensure_in_catalog).returns(@resource) @scope.stubs(:class_scope).with(@class) @compiler.evaluate_classes(%w{myclass}, @scope, false) end it "should skip classes that have already been evaluated" do @compiler.catalog.stubs(:tag) @scope.stubs(:class_scope).with(@class).returns(@scope) @compiler.expects(:add_resource).never @resource.expects(:evaluate).never Puppet::Parser::Resource.expects(:new).never @compiler.evaluate_classes(%w{myclass}, @scope, false) end it "should skip classes previously evaluated with different capitalization" do @compiler.catalog.stubs(:tag) @scope.stubs(:find_hostclass).with("MyClass").returns(@class) @scope.stubs(:class_scope).with(@class).returns(@scope) @compiler.expects(:add_resource).never @resource.expects(:evaluate).never Puppet::Parser::Resource.expects(:new).never @compiler.evaluate_classes(%w{MyClass}, @scope, false) end end describe "when evaluating AST nodes with no AST nodes present" do it "should do nothing" do @compiler.expects(:ast_nodes?).returns(false) @compiler.known_resource_types.expects(:nodes).never Puppet::Parser::Resource.expects(:new).never @compiler.send(:evaluate_ast_node) end end describe "when evaluating AST nodes with AST nodes present" do before do @compiler.known_resource_types.stubs(:nodes?).returns true # Set some names for our test @node.stubs(:names).returns(%w{a b c}) @compiler.known_resource_types.stubs(:node).with("a").returns(nil) @compiler.known_resource_types.stubs(:node).with("b").returns(nil) @compiler.known_resource_types.stubs(:node).with("c").returns(nil) # It should check this last, of course. @compiler.known_resource_types.stubs(:node).with("default").returns(nil) end it "should fail if the named node cannot be found" do proc { @compiler.send(:evaluate_ast_node) }.should raise_error(Puppet::ParseError) end it "should evaluate the first node class matching the node name" do node_class = stub 'node', :name => "c", :evaluate_code => nil @compiler.known_resource_types.stubs(:node).with("c").returns(node_class) node_resource = stub 'node resource', :ref => "Node[c]", :evaluate => nil, :type => "node" node_class.expects(:ensure_in_catalog).returns(node_resource) @compiler.compile end it "should match the default node if no matching node can be found" do node_class = stub 'node', :name => "default", :evaluate_code => nil @compiler.known_resource_types.stubs(:node).with("default").returns(node_class) node_resource = stub 'node resource', :ref => "Node[default]", :evaluate => nil, :type => "node" node_class.expects(:ensure_in_catalog).returns(node_resource) @compiler.compile end it "should evaluate the node resource immediately rather than using lazy evaluation" do node_class = stub 'node', :name => "c" @compiler.known_resource_types.stubs(:node).with("c").returns(node_class) node_resource = stub 'node resource', :ref => "Node[c]", :type => "node" node_class.expects(:ensure_in_catalog).returns(node_resource) node_resource.expects(:evaluate) @compiler.send(:evaluate_ast_node) end end describe "when evaluating node classes" do include PuppetSpec::Compiler describe "when provided classes in array format" do let(:node) { Puppet::Node.new('someone', :classes => ['something']) } describe "when the class exists" do it "should succeed if the class is already included" do manifest = <<-MANIFEST class something {} include something MANIFEST catalog = compile_to_catalog(manifest, node) catalog.resource('Class', 'Something').should_not be_nil end it "should evaluate the class without parameters if it's not already included" do manifest = "class something {}" catalog = compile_to_catalog(manifest, node) catalog.resource('Class', 'Something').should_not be_nil end end it "should fail if the class doesn't exist" do expect { compile_to_catalog('', node) }.to raise_error(Puppet::Error, /Could not find class something/) end end describe "when provided classes in hash format" do describe "for classes without parameters" do let(:node) { Puppet::Node.new('someone', :classes => {'something' => {}}) } describe "when the class exists" do it "should succeed if the class is already included" do manifest = <<-MANIFEST class something {} include something MANIFEST catalog = compile_to_catalog(manifest, node) catalog.resource('Class', 'Something').should_not be_nil end it "should evaluate the class if it's not already included" do manifest = <<-MANIFEST class something {} MANIFEST catalog = compile_to_catalog(manifest, node) catalog.resource('Class', 'Something').should_not be_nil end end it "should fail if the class doesn't exist" do expect { compile_to_catalog('', node) }.to raise_error(Puppet::Error, /Could not find class something/) end end describe "for classes with parameters" do let(:node) { Puppet::Node.new('someone', :classes => {'something' => {'configuron' => 'defrabulated'}}) } describe "when the class exists" do it "should fail if the class is already included" do manifest = <<-MANIFEST class something($configuron=frabulated) {} include something MANIFEST expect { compile_to_catalog(manifest, node) }.to raise_error(Puppet::Error, /Class\[Something\] is already declared/) end it "should evaluate the class if it's not already included" do manifest = <<-MANIFEST class something($configuron=frabulated) {} MANIFEST catalog = compile_to_catalog(manifest, node) resource = catalog.resource('Class', 'Something') resource['configuron'].should == 'defrabulated' end end it "should fail if the class doesn't exist" do expect { compile_to_catalog('', node) }.to raise_error(Puppet::Error, /Could not find class something/) end it 'evaluates classes declared with parameters before unparameterized classes' do node = Puppet::Node.new('someone', :classes => { 'app::web' => {}, 'app' => { 'port' => 8080 } }) manifest = <<-MANIFEST class app($port = 80) { } class app::web($port = $app::port) inherits app { notify { expected: message => "$port" } } MANIFEST catalog = compile_to_catalog(manifest, node) expect(catalog).to have_resource("Class[App]").with_parameter(:port, 8080) expect(catalog).to have_resource("Class[App::Web]") expect(catalog).to have_resource("Notify[expected]").with_parameter(:message, "8080") end end end end describe "when managing resource overrides" do before do @override = stub 'override', :ref => "File[/foo]", :type => "my" @resource = resource(:file, "/foo") end it "should be able to store overrides" do lambda { @compiler.add_override(@override) }.should_not raise_error end it "should apply overrides to the appropriate resources" do @compiler.add_resource(@scope, @resource) @resource.expects(:merge).with(@override) @compiler.add_override(@override) @compiler.compile end it "should accept overrides before the related resource has been created" do @resource.expects(:merge).with(@override) # First store the override @compiler.add_override(@override) # Then the resource @compiler.add_resource(@scope, @resource) # And compile, so they get resolved @compiler.compile end it "should fail if the compile is finished and resource overrides have not been applied" do @compiler.add_override(@override) lambda { @compiler.compile }.should raise_error Puppet::ParseError, 'Could not find resource(s) File[/foo] for overriding' end end end diff --git a/spec/unit/parser/functions/defined_spec.rb b/spec/unit/parser/functions/defined_spec.rb index c3c0c8ce8..4f54d9606 100755 --- a/spec/unit/parser/functions/defined_spec.rb +++ b/spec/unit/parser/functions/defined_spec.rb @@ -1,115 +1,115 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/pops' describe "the 'defined' function" do before :each do @compiler = Puppet::Parser::Compiler.new(Puppet::Node.new("foo")) @scope = Puppet::Parser::Scope.new(@compiler) end it "exists" do expect(Puppet::Parser::Functions.function("defined")).to be_eql("function_defined") end it "is true when the name is defined as a class" do @scope.known_resource_types.add Puppet::Resource::Type.new(:hostclass, "yayness") - expect(@scope.function_defined(["yayness"])).to be_true + expect(@scope.function_defined(["yayness"])).to be_truthy end it "is true when the name is defined as a definition" do @scope.known_resource_types.add Puppet::Resource::Type.new(:definition, "yayness") - expect(@scope.function_defined(["yayness"])).to be_true + expect(@scope.function_defined(["yayness"])).to be_truthy end it "is true when the name is defined as a builtin type" do - expect(@scope.function_defined(["file"])).to be_true + expect(@scope.function_defined(["file"])).to be_truthy end it "is true when any of the provided names are defined" do @scope.known_resource_types.add Puppet::Resource::Type.new(:definition, "yayness") - expect(@scope.function_defined(["meh", "yayness", "booness"])).to be_true + expect(@scope.function_defined(["meh", "yayness", "booness"])).to be_truthy end it "is false when a single given name is not defined" do - expect(@scope.function_defined(["meh"])).to be_false + expect(@scope.function_defined(["meh"])).to be_falsey end it "is false when none of the names are defined" do - expect(@scope.function_defined(["meh", "yayness", "booness"])).to be_false + expect(@scope.function_defined(["meh", "yayness", "booness"])).to be_falsey end it "is true when a resource reference is provided and the resource is in the catalog" do resource = Puppet::Resource.new("file", "/my/file") @compiler.add_resource(@scope, resource) - expect(@scope.function_defined([resource])).to be_true + expect(@scope.function_defined([resource])).to be_truthy end context "with string variable references" do it "is true when variable exists in scope" do @scope['x'] = 'something' - expect(@scope.function_defined(['$x'])).to be_true + expect(@scope.function_defined(['$x'])).to be_truthy end it "is true when absolute referenced variable exists in scope" do @compiler.topscope['x'] = 'something' # Without this magic linking, scope cannot find the global scope via the name '' # which is the name of "topscope". (This is one of many problems with the scope impl) # When running real code, scopes are always linked up this way. @scope.class_set('', @compiler.topscope) - expect(@scope.function_defined(['$::x'])).to be_true + expect(@scope.function_defined(['$::x'])).to be_truthy end it "is true when at least one variable exists in scope" do @scope['x'] = 'something' - expect(@scope.function_defined(['$y', '$x', '$z'])).to be_true + expect(@scope.function_defined(['$y', '$x', '$z'])).to be_truthy end it "is false when variable does not exist in scope" do - expect(@scope.function_defined(['$x'])).to be_false + expect(@scope.function_defined(['$x'])).to be_falsey end end it "is true when a resource type reference is provided, and the resource is in the catalog" do resource = Puppet::Resource.new("file", "/my/file") @compiler.add_resource(@scope, resource) resource_type = Puppet::Pops::Types::TypeFactory.resource('file', '/my/file') - expect(@scope.function_defined([resource_type])).to be_true + expect(@scope.function_defined([resource_type])).to be_truthy end it "raises an argument error if you ask if Resource is defined" do resource_type = Puppet::Pops::Types::TypeFactory.resource expect { @scope.function_defined([resource_type]) }.to raise_error(ArgumentError, /reference to all.*type/) end it "is true if referencing a built in type" do resource_type = Puppet::Pops::Types::TypeFactory.resource('file') - expect(@scope.function_defined([resource_type])).to be_true + expect(@scope.function_defined([resource_type])).to be_truthy end it "is true if referencing a defined type" do @scope.known_resource_types.add Puppet::Resource::Type.new(:definition, "yayness") resource_type = Puppet::Pops::Types::TypeFactory.resource('yayness') - expect(@scope.function_defined([resource_type])).to be_true + expect(@scope.function_defined([resource_type])).to be_truthy end it "is false if referencing an undefined type" do resource_type = Puppet::Pops::Types::TypeFactory.resource('barbershops') - expect(@scope.function_defined([resource_type])).to be_false + expect(@scope.function_defined([resource_type])).to be_falsey end it "is true when a class type is provided" do @scope.known_resource_types.add Puppet::Resource::Type.new(:hostclass, "cowabunga") class_type = Puppet::Pops::Types::TypeFactory.host_class("cowabunga") - expect(@scope.function_defined([class_type])).to be_true + expect(@scope.function_defined([class_type])).to be_truthy end it "raises an argument error if you ask if Class is defined" do class_type = Puppet::Pops::Types::TypeFactory.host_class expect { @scope.function_defined([class_type]) }.to raise_error(ArgumentError, /reference to all.*class/) end end diff --git a/spec/unit/parser/functions_spec.rb b/spec/unit/parser/functions_spec.rb index 3c6266752..e7a0496f4 100755 --- a/spec/unit/parser/functions_spec.rb +++ b/spec/unit/parser/functions_spec.rb @@ -1,132 +1,132 @@ #! /usr/bin/env ruby require 'spec_helper' describe Puppet::Parser::Functions do def callable_functions_from(mod) Class.new { include mod }.new end let(:function_module) { Puppet::Parser::Functions.environment_module(Puppet.lookup(:current_environment)) } let(:environment) { Puppet::Node::Environment.create(:myenv, []) } before do Puppet::Parser::Functions.reset end it "should have a method for returning an environment-specific module" do Puppet::Parser::Functions.environment_module(environment).should be_instance_of(Module) end describe "when calling newfunction" do it "should create the function in the environment module" do Puppet::Parser::Functions.newfunction("name", :type => :rvalue) { |args| } function_module.should be_method_defined :function_name end it "should warn if the function already exists" do Puppet::Parser::Functions.newfunction("name", :type => :rvalue) { |args| } Puppet.expects(:warning) Puppet::Parser::Functions.newfunction("name", :type => :rvalue) { |args| } end it "should raise an error if the function type is not correct" do expect { Puppet::Parser::Functions.newfunction("name", :type => :unknown) { |args| } }.to raise_error Puppet::DevError, "Invalid statement type :unknown" end it "instruments the function to profile the execution" do messages = [] Puppet::Util::Profiler.add_profiler(Puppet::Util::Profiler::WallClock.new(proc { |msg| messages << msg }, "id")) Puppet::Parser::Functions.newfunction("name", :type => :rvalue) { |args| } callable_functions_from(function_module).function_name([]) messages.first.should =~ /Called name/ end end describe "when calling function to test function existence" do it "should return false if the function doesn't exist" do Puppet::Parser::Functions.autoloader.stubs(:load) - Puppet::Parser::Functions.function("name").should be_false + Puppet::Parser::Functions.function("name").should be_falsey end it "should return its name if the function exists" do Puppet::Parser::Functions.newfunction("name", :type => :rvalue) { |args| } Puppet::Parser::Functions.function("name").should == "function_name" end it "should try to autoload the function if it doesn't exist yet" do Puppet::Parser::Functions.autoloader.expects(:load) Puppet::Parser::Functions.function("name") end it "combines functions from the root with those from the current environment" do Puppet.override(:current_environment => Puppet.lookup(:root_environment)) do Puppet::Parser::Functions.newfunction("onlyroot", :type => :rvalue) do |args| end end Puppet.override(:current_environment => Puppet::Node::Environment.create(:other, [])) do Puppet::Parser::Functions.newfunction("other_env", :type => :rvalue) do |args| end expect(Puppet::Parser::Functions.function("onlyroot")).to eq("function_onlyroot") expect(Puppet::Parser::Functions.function("other_env")).to eq("function_other_env") end - expect(Puppet::Parser::Functions.function("other_env")).to be_false + expect(Puppet::Parser::Functions.function("other_env")).to be_falsey end end describe "when calling function to test arity" do let(:function_module) { Puppet::Parser::Functions.environment_module(Puppet.lookup(:current_environment)) } it "should raise an error if the function is called with too many arguments" do Puppet::Parser::Functions.newfunction("name", :arity => 2) { |args| } expect { callable_functions_from(function_module).function_name([1,2,3]) }.to raise_error ArgumentError end it "should raise an error if the function is called with too few arguments" do Puppet::Parser::Functions.newfunction("name", :arity => 2) { |args| } expect { callable_functions_from(function_module).function_name([1]) }.to raise_error ArgumentError end it "should not raise an error if the function is called with correct number of arguments" do Puppet::Parser::Functions.newfunction("name", :arity => 2) { |args| } expect { callable_functions_from(function_module).function_name([1,2]) }.to_not raise_error end it "should raise an error if the variable arg function is called with too few arguments" do Puppet::Parser::Functions.newfunction("name", :arity => -3) { |args| } expect { callable_functions_from(function_module).function_name([1]) }.to raise_error ArgumentError end it "should not raise an error if the variable arg function is called with correct number of arguments" do Puppet::Parser::Functions.newfunction("name", :arity => -3) { |args| } expect { callable_functions_from(function_module).function_name([1,2]) }.to_not raise_error end it "should not raise an error if the variable arg function is called with more number of arguments" do Puppet::Parser::Functions.newfunction("name", :arity => -3) { |args| } expect { callable_functions_from(function_module).function_name([1,2,3]) }.to_not raise_error end end describe "::arity" do it "returns the given arity of a function" do Puppet::Parser::Functions.newfunction("name", :arity => 4) { |args| } Puppet::Parser::Functions.arity(:name).should == 4 end it "returns -1 if no arity is given" do Puppet::Parser::Functions.newfunction("name") { |args| } Puppet::Parser::Functions.arity(:name).should == -1 end end end diff --git a/spec/unit/parser/relationship_spec.rb b/spec/unit/parser/relationship_spec.rb index 890b35e21..4aca907f0 100755 --- a/spec/unit/parser/relationship_spec.rb +++ b/spec/unit/parser/relationship_spec.rb @@ -1,75 +1,75 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/parser/relationship' describe Puppet::Parser::Relationship do before do @source = Puppet::Resource.new(:mytype, "source") @target = Puppet::Resource.new(:mytype, "target") @extra_resource = Puppet::Resource.new(:mytype, "extra") @extra_resource2 = Puppet::Resource.new(:mytype, "extra2") @dep = Puppet::Parser::Relationship.new(@source, @target, :relationship) end describe "when evaluating" do before do @catalog = Puppet::Resource::Catalog.new @catalog.add_resource(@source) @catalog.add_resource(@target) @catalog.add_resource(@extra_resource) @catalog.add_resource(@extra_resource2) end it "should fail if the source resource cannot be found" do @catalog = Puppet::Resource::Catalog.new @catalog.add_resource @target lambda { @dep.evaluate(@catalog) }.should raise_error(ArgumentError) end it "should fail if the target resource cannot be found" do @catalog = Puppet::Resource::Catalog.new @catalog.add_resource @source lambda { @dep.evaluate(@catalog) }.should raise_error(ArgumentError) end it "should add the target as a 'before' value if the type is 'relationship'" do @dep.type = :relationship @dep.evaluate(@catalog) @source[:before].should be_include("Mytype[target]") end it "should add the target as a 'notify' value if the type is 'subscription'" do @dep.type = :subscription @dep.evaluate(@catalog) @source[:notify].should be_include("Mytype[target]") end it "should supplement rather than clobber existing relationship values" do @source[:before] = "File[/bar]" @dep.evaluate(@catalog) # this test did not work before. It was appending the resources # together as a string - (@source[:before].class == Array).should be_true + (@source[:before].class == Array).should be_truthy @source[:before].should be_include("Mytype[target]") @source[:before].should be_include("File[/bar]") end it "should supplement rather than clobber existing resource relationships" do @source[:before] = @extra_resource @dep.evaluate(@catalog) - (@source[:before].class == Array).should be_true + (@source[:before].class == Array).should be_truthy @source[:before].should be_include("Mytype[target]") @source[:before].should be_include(@extra_resource) end it "should supplement rather than clobber multiple existing resource relationships" do @source[:before] = [@extra_resource, @extra_resource2] @dep.evaluate(@catalog) - (@source[:before].class == Array).should be_true + (@source[:before].class == Array).should be_truthy @source[:before].should be_include("Mytype[target]") @source[:before].should be_include(@extra_resource) @source[:before].should be_include(@extra_resource2) end end end diff --git a/spec/unit/parser/resource_spec.rb b/spec/unit/parser/resource_spec.rb index 97f19e21a..c777d249c 100755 --- a/spec/unit/parser/resource_spec.rb +++ b/spec/unit/parser/resource_spec.rb @@ -1,582 +1,582 @@ require 'spec_helper' describe Puppet::Parser::Resource do before do environment = Puppet::Node::Environment.create(:testing, []) @node = Puppet::Node.new("yaynode", :environment => environment) @known_resource_types = environment.known_resource_types @compiler = Puppet::Parser::Compiler.new(@node) @source = newclass "" @scope = @compiler.topscope end def mkresource(args = {}) args[:source] ||= @source args[:scope] ||= @scope params = args[:parameters] || {:one => "yay", :three => "rah"} if args[:parameters] == :none args.delete(:parameters) elsif not args[:parameters].is_a? Array args[:parameters] = paramify(args[:source], params) end Puppet::Parser::Resource.new("resource", "testing", args) end def param(name, value, source) Puppet::Parser::Resource::Param.new(:name => name, :value => value, :source => source) end def paramify(source, hash) hash.collect do |name, value| Puppet::Parser::Resource::Param.new( :name => name, :value => value, :source => source ) end end def newclass(name) @known_resource_types.add Puppet::Resource::Type.new(:hostclass, name) end def newdefine(name) @known_resource_types.add Puppet::Resource::Type.new(:definition, name) end def newnode(name) @known_resource_types.add Puppet::Resource::Type.new(:node, name) end it "should get its environment from its scope" do scope = stub 'scope', :source => stub("source"), :namespaces => nil scope.expects(:environment).returns("foo").at_least_once Puppet::Parser::Resource.new("file", "whatever", :scope => scope).environment.should == "foo" end it "should use the resource type collection helper module" do Puppet::Parser::Resource.ancestors.should be_include(Puppet::Resource::TypeCollectionHelper) end it "should use the scope's environment as its environment" do @scope.expects(:environment).returns("myenv").at_least_once Puppet::Parser::Resource.new("file", "whatever", :scope => @scope).environment.should == "myenv" end it "should be isomorphic if it is builtin and models an isomorphic type" do Puppet::Type.type(:file).expects(:isomorphic?).returns(true) - @resource = Puppet::Parser::Resource.new("file", "whatever", :scope => @scope, :source => @source).isomorphic?.should be_true + @resource = Puppet::Parser::Resource.new("file", "whatever", :scope => @scope, :source => @source).isomorphic?.should be_truthy end it "should not be isomorphic if it is builtin and models a non-isomorphic type" do Puppet::Type.type(:file).expects(:isomorphic?).returns(false) - @resource = Puppet::Parser::Resource.new("file", "whatever", :scope => @scope, :source => @source).isomorphic?.should be_false + @resource = Puppet::Parser::Resource.new("file", "whatever", :scope => @scope, :source => @source).isomorphic?.should be_falsey end it "should be isomorphic if it is not builtin" do newdefine "whatever" - @resource = Puppet::Parser::Resource.new("whatever", "whatever", :scope => @scope, :source => @source).isomorphic?.should be_true + @resource = Puppet::Parser::Resource.new("whatever", "whatever", :scope => @scope, :source => @source).isomorphic?.should be_truthy end it "should have an array-indexing method for retrieving parameter values" do @resource = mkresource @resource[:one].should == "yay" end it "should use a Puppet::Resource for converting to a ral resource" do trans = mock 'resource', :to_ral => "yay" @resource = mkresource @resource.expects(:copy_as_resource).returns trans @resource.to_ral.should == "yay" end it "should be able to use the indexing operator to access parameters" do resource = Puppet::Parser::Resource.new("resource", "testing", :source => "source", :scope => @scope) resource["foo"] = "bar" resource["foo"].should == "bar" end it "should return the title when asked for a parameter named 'title'" do Puppet::Parser::Resource.new("resource", "testing", :source => @source, :scope => @scope)[:title].should == "testing" end describe "when initializing" do before do @arguments = {:scope => @scope} end it "should fail unless #{name.to_s} is specified" do expect { Puppet::Parser::Resource.new('file', '/my/file') }.to raise_error(ArgumentError, /Resources require a hash as last argument/) end it "should set the reference correctly" do res = Puppet::Parser::Resource.new("resource", "testing", @arguments) res.ref.should == "Resource[testing]" end it "should be tagged with user tags" do tags = [ "tag1", "tag2" ] @arguments[:parameters] = [ param(:tag, tags , :source) ] res = Puppet::Parser::Resource.new("resource", "testing", @arguments) res.should be_tagged("tag1") res.should be_tagged("tag2") end end describe "when evaluating" do before do @catalog = Puppet::Resource::Catalog.new source = stub('source') source.stubs(:module_name) @scope = Puppet::Parser::Scope.new(@compiler, :source => source) @catalog.add_resource(Puppet::Parser::Resource.new("stage", :main, :scope => @scope)) end it "should evaluate the associated AST definition" do definition = newdefine "mydefine" res = Puppet::Parser::Resource.new("mydefine", "whatever", :scope => @scope, :source => @source, :catalog => @catalog) definition.expects(:evaluate_code).with(res) res.evaluate end it "should evaluate the associated AST class" do @class = newclass "myclass" res = Puppet::Parser::Resource.new("class", "myclass", :scope => @scope, :source => @source, :catalog => @catalog) @class.expects(:evaluate_code).with(res) res.evaluate end it "should evaluate the associated AST node" do nodedef = newnode("mynode") res = Puppet::Parser::Resource.new("node", "mynode", :scope => @scope, :source => @source, :catalog => @catalog) nodedef.expects(:evaluate_code).with(res) res.evaluate end it "should add an edge to any specified stage for class resources" do @compiler.known_resource_types.add Puppet::Resource::Type.new(:hostclass, "foo", {}) other_stage = Puppet::Parser::Resource.new(:stage, "other", :scope => @scope, :catalog => @catalog) @compiler.add_resource(@scope, other_stage) resource = Puppet::Parser::Resource.new(:class, "foo", :scope => @scope, :catalog => @catalog) resource[:stage] = 'other' @compiler.add_resource(@scope, resource) resource.evaluate - @compiler.catalog.edge?(other_stage, resource).should be_true + @compiler.catalog.edge?(other_stage, resource).should be_truthy end it "should fail if an unknown stage is specified" do @compiler.known_resource_types.add Puppet::Resource::Type.new(:hostclass, "foo", {}) resource = Puppet::Parser::Resource.new(:class, "foo", :scope => @scope, :catalog => @catalog) resource[:stage] = 'other' expect { resource.evaluate }.to raise_error(ArgumentError, /Could not find stage other specified by/) end it "should add edges from the class resources to the parent's stage if no stage is specified" do main = @compiler.catalog.resource(:stage, :main) foo_stage = Puppet::Parser::Resource.new(:stage, :foo_stage, :scope => @scope, :catalog => @catalog) @compiler.add_resource(@scope, foo_stage) @compiler.known_resource_types.add Puppet::Resource::Type.new(:hostclass, "foo", {}) resource = Puppet::Parser::Resource.new(:class, "foo", :scope => @scope, :catalog => @catalog) resource[:stage] = 'foo_stage' @compiler.add_resource(@scope, resource) resource.evaluate @compiler.catalog.should be_edge(foo_stage, resource) end it "should allow edges to propagate multiple levels down the scope hierarchy" do Puppet[:code] = <<-MANIFEST stage { before: before => Stage[main] } class alpha { include beta } class beta { include gamma } class gamma { } class { alpha: stage => before } MANIFEST catalog = Puppet::Parser::Compiler.compile(Puppet::Node.new 'anyone') # Stringify them to make for easier lookup edges = catalog.edges.map {|e| [e.source.ref, e.target.ref]} edges.should include(["Stage[before]", "Class[Alpha]"]) edges.should include(["Stage[before]", "Class[Beta]"]) edges.should include(["Stage[before]", "Class[Gamma]"]) end it "should use the specified stage even if the parent scope specifies one" do Puppet[:code] = <<-MANIFEST stage { before: before => Stage[main], } stage { after: require => Stage[main], } class alpha { class { beta: stage => after } } class beta { } class { alpha: stage => before } MANIFEST catalog = Puppet::Parser::Compiler.compile(Puppet::Node.new 'anyone') edges = catalog.edges.map {|e| [e.source.ref, e.target.ref]} edges.should include(["Stage[before]", "Class[Alpha]"]) edges.should include(["Stage[after]", "Class[Beta]"]) end it "should add edges from top-level class resources to the main stage if no stage is specified" do main = @compiler.catalog.resource(:stage, :main) @compiler.known_resource_types.add Puppet::Resource::Type.new(:hostclass, "foo", {}) resource = Puppet::Parser::Resource.new(:class, "foo", :scope => @scope, :catalog => @catalog) @compiler.add_resource(@scope, resource) resource.evaluate @compiler.catalog.should be_edge(main, resource) end end describe "when finishing" do before do @class = newclass "myclass" @nodedef = newnode("mynode") @resource = Puppet::Parser::Resource.new("file", "whatever", :scope => @scope, :source => @source) end it "should do nothing if it has already been finished" do @resource.finish @resource.expects(:add_defaults).never @resource.finish end it "should add all defaults available from the scope" do @resource.scope.expects(:lookupdefaults).with(@resource.type).returns(:owner => param(:owner, "default", @resource.source)) @resource.finish @resource[:owner].should == "default" end it "should not replace existing parameters with defaults" do @resource.set_parameter :owner, "oldvalue" @resource.scope.expects(:lookupdefaults).with(@resource.type).returns(:owner => :replaced) @resource.finish @resource[:owner].should == "oldvalue" end it "should add a copy of each default, rather than the actual default parameter instance" do newparam = param(:owner, "default", @resource.source) other = newparam.dup other.value = "other" newparam.expects(:dup).returns(other) @resource.scope.expects(:lookupdefaults).with(@resource.type).returns(:owner => newparam) @resource.finish @resource[:owner].should == "other" end end describe "when being tagged" do before do @scope_resource = stub 'scope_resource', :tags => %w{srone srtwo} @scope.stubs(:resource).returns @scope_resource @resource = Puppet::Parser::Resource.new("file", "yay", :scope => @scope, :source => mock('source')) end it "should get tagged with the resource type" do @resource.tags.should be_include("file") end it "should get tagged with the title" do @resource.tags.should be_include("yay") end it "should get tagged with each name in the title if the title is a qualified class name" do resource = Puppet::Parser::Resource.new("file", "one::two", :scope => @scope, :source => mock('source')) resource.tags.should be_include("one") resource.tags.should be_include("two") end it "should get tagged with each name in the type if the type is a qualified class name" do resource = Puppet::Parser::Resource.new("one::two", "whatever", :scope => @scope, :source => mock('source')) resource.tags.should be_include("one") resource.tags.should be_include("two") end it "should not get tagged with non-alphanumeric titles" do resource = Puppet::Parser::Resource.new("file", "this is a test", :scope => @scope, :source => mock('source')) resource.tags.should_not be_include("this is a test") end it "should fail on tags containing '*' characters" do expect { @resource.tag("bad*tag") }.to raise_error(Puppet::ParseError) end it "should fail on tags starting with '-' characters" do expect { @resource.tag("-badtag") }.to raise_error(Puppet::ParseError) end it "should fail on tags containing ' ' characters" do expect { @resource.tag("bad tag") }.to raise_error(Puppet::ParseError) end it "should allow alpha tags" do expect { @resource.tag("good_tag") }.to_not raise_error end end describe "when merging overrides" do before do @source = "source1" @resource = mkresource :source => @source @override = mkresource :source => @source end it "should fail when the override was not created by a parent class" do @override.source = "source2" @override.source.expects(:child_of?).with("source1").returns(false) expect { @resource.merge(@override) }.to raise_error(Puppet::ParseError) end it "should succeed when the override was created in the current scope" do @resource.source = "source3" @override.source = @resource.source @override.source.expects(:child_of?).with("source3").never params = {:a => :b, :c => :d} @override.expects(:parameters).returns(params) @resource.expects(:override_parameter).with(:b) @resource.expects(:override_parameter).with(:d) @resource.merge(@override) end it "should succeed when a parent class created the override" do @resource.source = "source3" @override.source = "source4" @override.source.expects(:child_of?).with("source3").returns(true) params = {:a => :b, :c => :d} @override.expects(:parameters).returns(params) @resource.expects(:override_parameter).with(:b) @resource.expects(:override_parameter).with(:d) @resource.merge(@override) end it "should add new parameters when the parameter is not set" do @source.stubs(:child_of?).returns true @override.set_parameter(:testing, "value") @resource.merge(@override) @resource[:testing].should == "value" end it "should replace existing parameter values" do @source.stubs(:child_of?).returns true @resource.set_parameter(:testing, "old") @override.set_parameter(:testing, "value") @resource.merge(@override) @resource[:testing].should == "value" end it "should add values to the parameter when the override was created with the '+>' syntax" do @source.stubs(:child_of?).returns true param = Puppet::Parser::Resource::Param.new(:name => :testing, :value => "testing", :source => @resource.source) param.add = true @override.set_parameter(param) @resource.set_parameter(:testing, "other") @resource.merge(@override) @resource[:testing].should == %w{other testing} end it "should not merge parameter values when multiple resources are overriden with '+>' at once " do @resource_2 = mkresource :source => @source @resource. set_parameter(:testing, "old_val_1") @resource_2.set_parameter(:testing, "old_val_2") @source.stubs(:child_of?).returns true param = Puppet::Parser::Resource::Param.new(:name => :testing, :value => "new_val", :source => @resource.source) param.add = true @override.set_parameter(param) @resource. merge(@override) @resource_2.merge(@override) @resource [:testing].should == %w{old_val_1 new_val} @resource_2[:testing].should == %w{old_val_2 new_val} end it "should promote tag overrides to real tags" do @source.stubs(:child_of?).returns true param = Puppet::Parser::Resource::Param.new(:name => :tag, :value => "testing", :source => @resource.source) @override.set_parameter(param) @resource.merge(@override) - @resource.tagged?("testing").should be_true + @resource.tagged?("testing").should be_truthy end end it "should be able to be converted to a normal resource" do @source = stub 'scope', :name => "myscope" @resource = mkresource :source => @source @resource.should respond_to(:copy_as_resource) end describe "when being converted to a resource" do before do @parser_resource = mkresource :scope => @scope, :parameters => {:foo => "bar", :fee => "fum"} end it "should create an instance of Puppet::Resource" do @parser_resource.copy_as_resource.should be_instance_of(Puppet::Resource) end it "should set the type correctly on the Puppet::Resource" do @parser_resource.copy_as_resource.type.should == @parser_resource.type end it "should set the title correctly on the Puppet::Resource" do @parser_resource.copy_as_resource.title.should == @parser_resource.title end it "should copy over all of the parameters" do result = @parser_resource.copy_as_resource.to_hash # The name will be in here, also. result[:foo].should == "bar" result[:fee].should == "fum" end it "should copy over the tags" do @parser_resource.tag "foo" @parser_resource.tag "bar" @parser_resource.copy_as_resource.tags.should == @parser_resource.tags end it "should copy over the line" do @parser_resource.line = 40 @parser_resource.copy_as_resource.line.should == 40 end it "should copy over the file" do @parser_resource.file = "/my/file" @parser_resource.copy_as_resource.file.should == "/my/file" end it "should copy over the 'exported' value" do @parser_resource.exported = true - @parser_resource.copy_as_resource.exported.should be_true + @parser_resource.copy_as_resource.exported.should be_truthy end it "should copy over the 'virtual' value" do @parser_resource.virtual = true - @parser_resource.copy_as_resource.virtual.should be_true + @parser_resource.copy_as_resource.virtual.should be_truthy end it "should convert any parser resource references to Puppet::Resource instances" do ref = Puppet::Resource.new("file", "/my/file") @parser_resource = mkresource :source => @source, :parameters => {:foo => "bar", :fee => ref} result = @parser_resource.copy_as_resource result[:fee].should == Puppet::Resource.new(:file, "/my/file") end it "should convert any parser resource references to Puppet::Resource instances even if they are in an array" do ref = Puppet::Resource.new("file", "/my/file") @parser_resource = mkresource :source => @source, :parameters => {:foo => "bar", :fee => ["a", ref]} result = @parser_resource.copy_as_resource result[:fee].should == ["a", Puppet::Resource.new(:file, "/my/file")] end it "should convert any parser resource references to Puppet::Resource instances even if they are in an array of array, and even deeper" do ref1 = Puppet::Resource.new("file", "/my/file1") ref2 = Puppet::Resource.new("file", "/my/file2") @parser_resource = mkresource :source => @source, :parameters => {:foo => "bar", :fee => ["a", [ref1,ref2]]} result = @parser_resource.copy_as_resource result[:fee].should == ["a", Puppet::Resource.new(:file, "/my/file1"), Puppet::Resource.new(:file, "/my/file2")] end it "should fail if the same param is declared twice" do lambda do @parser_resource = mkresource :source => @source, :parameters => [ Puppet::Parser::Resource::Param.new( :name => :foo, :value => "bar", :source => @source ), Puppet::Parser::Resource::Param.new( :name => :foo, :value => "baz", :source => @source ) ] end.should raise_error(Puppet::ParseError) end end describe "when validating" do it "should check each parameter" do resource = Puppet::Parser::Resource.new :foo, "bar", :scope => @scope, :source => stub("source") resource[:one] = :two resource[:three] = :four resource.expects(:validate_parameter).with(:one) resource.expects(:validate_parameter).with(:three) resource.send(:validate) end it "should raise a parse error when there's a failure" do resource = Puppet::Parser::Resource.new :foo, "bar", :scope => @scope, :source => stub("source") resource[:one] = :two resource.expects(:validate_parameter).with(:one).raises ArgumentError expect { resource.send(:validate) }.to raise_error(Puppet::ParseError) end end describe "when setting parameters" do before do @source = newclass "foobar" @resource = Puppet::Parser::Resource.new :foo, "bar", :scope => @scope, :source => @source end it "should accept Param instances and add them to the parameter list" do param = Puppet::Parser::Resource::Param.new :name => "foo", :value => "bar", :source => @source @resource.set_parameter(param) @resource["foo"].should == "bar" end it "should allow parameters to be set to 'false'" do @resource.set_parameter("myparam", false) - @resource["myparam"].should be_false + @resource["myparam"].should be_falsey end it "should use its source when provided a parameter name and value" do @resource.set_parameter("myparam", "myvalue") @resource["myparam"].should == "myvalue" end end # part of #629 -- the undef keyword. Make sure 'undef' params get skipped. it "should not include 'undef' parameters when converting itself to a hash" do resource = Puppet::Parser::Resource.new "file", "/tmp/testing", :source => mock("source"), :scope => mock("scope") resource[:owner] = :undef resource[:mode] = "755" resource.to_hash[:owner].should be_nil end end diff --git a/spec/unit/parser/scope_spec.rb b/spec/unit/parser/scope_spec.rb index 96b53f538..06094e048 100755 --- a/spec/unit/parser/scope_spec.rb +++ b/spec/unit/parser/scope_spec.rb @@ -1,658 +1,658 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/compiler' require 'puppet_spec/scope' describe Puppet::Parser::Scope do include PuppetSpec::Scope before :each do @scope = Puppet::Parser::Scope.new( Puppet::Parser::Compiler.new(Puppet::Node.new("foo")) ) @scope.source = Puppet::Resource::Type.new(:node, :foo) @topscope = @scope.compiler.topscope @scope.parent = @topscope end describe "create_test_scope_for_node" do let(:node_name) { "node_name_foo" } let(:scope) { create_test_scope_for_node(node_name) } it "should be a kind of Scope" do scope.should be_a_kind_of(Puppet::Parser::Scope) end it "should set the source to a node resource" do scope.source.should be_a_kind_of(Puppet::Resource::Type) end it "should have a compiler" do scope.compiler.should be_a_kind_of(Puppet::Parser::Compiler) end it "should set the parent to the compiler topscope" do scope.parent.should be(scope.compiler.topscope) end end it "should return a scope for use in a test harness" do create_test_scope_for_node("node_name_foo").should be_a_kind_of(Puppet::Parser::Scope) end it "should be able to retrieve class scopes by name" do @scope.class_set "myname", "myscope" @scope.class_scope("myname").should == "myscope" end it "should be able to retrieve class scopes by object" do klass = mock 'ast_class' klass.expects(:name).returns("myname") @scope.class_set "myname", "myscope" @scope.class_scope(klass).should == "myscope" end it "should be able to retrieve its parent module name from the source of its parent type" do @topscope.source = Puppet::Resource::Type.new(:hostclass, :foo, :module_name => "foo") @scope.parent_module_name.should == "foo" end it "should return a nil parent module name if it has no parent" do @topscope.parent_module_name.should be_nil end it "should return a nil parent module name if its parent has no source" do @scope.parent_module_name.should be_nil end it "should get its environment from its compiler" do env = Puppet::Node::Environment.create(:testing, []) compiler = stub 'compiler', :environment => env, :is_a? => true scope = Puppet::Parser::Scope.new(compiler) scope.environment.should equal(env) end it "should fail if no compiler is supplied" do expect { Puppet::Parser::Scope.new }.to raise_error(ArgumentError, /wrong number of arguments/) end it "should fail if something that isn't a compiler is supplied" do expect { Puppet::Parser::Scope.new(:compiler => true) }.to raise_error(Puppet::DevError, /you must pass a compiler instance/) end it "should use the resource type collection helper to find its known resource types" do Puppet::Parser::Scope.ancestors.should include(Puppet::Resource::TypeCollectionHelper) end describe "when custom functions are called" do let(:env) { Puppet::Node::Environment.create(:testing, []) } let(:compiler) { Puppet::Parser::Compiler.new(Puppet::Node.new('foo', :environment => env)) } let(:scope) { Puppet::Parser::Scope.new(compiler) } it "calls methods prefixed with function_ as custom functions" do scope.function_sprintf(["%b", 123]).should == "1111011" end it "raises an error when arguments are not passed in an Array" do expect do scope.function_sprintf("%b", 123) end.to raise_error ArgumentError, /custom functions must be called with a single array that contains the arguments/ end it "raises an error on subsequent calls when arguments are not passed in an Array" do scope.function_sprintf(["first call"]) expect do scope.function_sprintf("%b", 123) end.to raise_error ArgumentError, /custom functions must be called with a single array that contains the arguments/ end it "raises NoMethodError when the not prefixed" do expect { scope.sprintf(["%b", 123]) }.to raise_error(NoMethodError) end it "raises NoMethodError when prefixed with function_ but it doesn't exist" do expect { scope.function_fake_bs(['cows']) }.to raise_error(NoMethodError) end end describe "when initializing" do it "should extend itself with its environment's Functions module as well as the default" do env = Puppet::Node::Environment.create(:myenv, []) root = Puppet.lookup(:root_environment) compiler = stub 'compiler', :environment => env, :is_a? => true scope = Puppet::Parser::Scope.new(compiler) scope.singleton_class.ancestors.should be_include(Puppet::Parser::Functions.environment_module(env)) scope.singleton_class.ancestors.should be_include(Puppet::Parser::Functions.environment_module(root)) end it "should extend itself with the default Functions module if its environment is the default" do root = Puppet.lookup(:root_environment) node = Puppet::Node.new('localhost') compiler = Puppet::Parser::Compiler.new(node) scope = Puppet::Parser::Scope.new(compiler) scope.singleton_class.ancestors.should be_include(Puppet::Parser::Functions.environment_module(root)) end end describe "when looking up a variable" do it "should support :lookupvar and :setvar for backward compatibility" do @scope.setvar("var", "yep") @scope.lookupvar("var").should == "yep" end it "should fail if invoked with a non-string name" do expect { @scope[:foo] }.to raise_error(Puppet::ParseError, /Scope variable name .* not a string/) expect { @scope[:foo] = 12 }.to raise_error(Puppet::ParseError, /Scope variable name .* not a string/) end it "should return nil for unset variables" do @scope["var"].should be_nil end it "answers exist? with boolean false for non existing variables" do expect(@scope.exist?("var")).to be(false) end it "answers exist? with boolean false for non existing variables" do @scope["var"] = "yep" expect(@scope.exist?("var")).to be(true) end it "should be able to look up values" do @scope["var"] = "yep" @scope["var"].should == "yep" end it "should be able to look up hashes" do @scope["var"] = {"a" => "b"} @scope["var"].should == {"a" => "b"} end it "should be able to look up variables in parent scopes" do @topscope["var"] = "parentval" @scope["var"].should == "parentval" end it "should prefer its own values to parent values" do @topscope["var"] = "parentval" @scope["var"] = "childval" @scope["var"].should == "childval" end it "should be able to detect when variables are set" do @scope["var"] = "childval" @scope.should be_include("var") end it "does not allow changing a set value" do @scope["var"] = "childval" expect { @scope["var"] = "change" }.to raise_error(Puppet::Error, "Cannot reassign variable var") end it "should be able to detect when variables are not set" do @scope.should_not be_include("var") end describe "and the variable is qualified" do before :each do @known_resource_types = @scope.known_resource_types node = Puppet::Node.new('localhost') @compiler = Puppet::Parser::Compiler.new(node) end def newclass(name) @known_resource_types.add Puppet::Resource::Type.new(:hostclass, name) end def create_class_scope(name) klass = newclass(name) catalog = Puppet::Resource::Catalog.new catalog.add_resource(Puppet::Parser::Resource.new("stage", :main, :scope => Puppet::Parser::Scope.new(@compiler))) Puppet::Parser::Resource.new("class", name, :scope => @scope, :source => mock('source'), :catalog => catalog).evaluate @scope.class_scope(klass) end it "should be able to look up explicitly fully qualified variables from main" do Puppet.expects(:deprecation_warning).never other_scope = create_class_scope("") other_scope["othervar"] = "otherval" @scope["::othervar"].should == "otherval" end it "should be able to look up explicitly fully qualified variables from other scopes" do Puppet.expects(:deprecation_warning).never other_scope = create_class_scope("other") other_scope["var"] = "otherval" @scope["::other::var"].should == "otherval" end it "should be able to look up deeply qualified variables" do Puppet.expects(:deprecation_warning).never other_scope = create_class_scope("other::deep::klass") other_scope["var"] = "otherval" @scope["other::deep::klass::var"].should == "otherval" end it "should return nil for qualified variables that cannot be found in other classes" do other_scope = create_class_scope("other::deep::klass") @scope["other::deep::klass::var"].should be_nil end it "should warn and return nil for qualified variables whose classes have not been evaluated" do klass = newclass("other::deep::klass") @scope.expects(:warning) @scope["other::deep::klass::var"].should be_nil end it "should warn and return nil for qualified variables whose classes do not exist" do @scope.expects(:warning) @scope["other::deep::klass::var"].should be_nil end it "should return nil when asked for a non-string qualified variable from a class that does not exist" do @scope.stubs(:warning) @scope["other::deep::klass::var"].should be_nil end it "should return nil when asked for a non-string qualified variable from a class that has not been evaluated" do @scope.stubs(:warning) klass = newclass("other::deep::klass") @scope["other::deep::klass::var"].should be_nil end end context "and strict_variables is true" do before(:each) do Puppet[:strict_variables] = true end it "should throw a symbol when unknown variable is looked up" do expect { @scope['john_doe'] }.to throw_symbol(:undefined_variable) end it "should throw a symbol when unknown qualified variable is looked up" do expect { @scope['nowhere::john_doe'] }.to throw_symbol(:undefined_variable) end end end describe "when variables are set with append=true" do it "should raise error if the variable is already defined in this scope" do @scope.setvar("var", "1", :append => false) expect { @scope.setvar("var", "1", :append => true) }.to raise_error( Puppet::ParseError, "Cannot append, variable var is defined in this scope" ) end it "should lookup current variable value" do @scope.expects(:[]).with("var").returns("2") @scope.setvar("var", "1", :append => true) end it "should store the concatenated string '42'" do @topscope.setvar("var", "4", :append => false) @scope.setvar("var", "2", :append => true) @scope["var"].should == "42" end it "should store the concatenated array [4,2]" do @topscope.setvar("var", [4], :append => false) @scope.setvar("var", [2], :append => true) @scope["var"].should == [4,2] end it "should store the merged hash {a => b, c => d}" do @topscope.setvar("var", {"a" => "b"}, :append => false) @scope.setvar("var", {"c" => "d"}, :append => true) @scope["var"].should == {"a" => "b", "c" => "d"} end it "should raise an error when appending a hash with something other than another hash" do @topscope.setvar("var", {"a" => "b"}, :append => false) expect { @scope.setvar("var", "not a hash", :append => true) }.to raise_error( ArgumentError, "Trying to append to a hash with something which is not a hash is unsupported" ) end end describe "when calling number?" do it "should return nil if called with anything not a number" do Puppet::Parser::Scope.number?([2]).should be_nil end it "should return a Fixnum for a Fixnum" do Puppet::Parser::Scope.number?(2).should be_an_instance_of(Fixnum) end it "should return a Float for a Float" do Puppet::Parser::Scope.number?(2.34).should be_an_instance_of(Float) end it "should return 234 for '234'" do Puppet::Parser::Scope.number?("234").should == 234 end it "should return nil for 'not a number'" do Puppet::Parser::Scope.number?("not a number").should be_nil end it "should return 23.4 for '23.4'" do Puppet::Parser::Scope.number?("23.4").should == 23.4 end it "should return 23.4e13 for '23.4e13'" do Puppet::Parser::Scope.number?("23.4e13").should == 23.4e13 end it "should understand negative numbers" do Puppet::Parser::Scope.number?("-234").should == -234 end it "should know how to convert exponential float numbers ala '23e13'" do Puppet::Parser::Scope.number?("23e13").should == 23e13 end it "should understand hexadecimal numbers" do Puppet::Parser::Scope.number?("0x234").should == 0x234 end it "should understand octal numbers" do Puppet::Parser::Scope.number?("0755").should == 0755 end it "should return nil on malformed integers" do Puppet::Parser::Scope.number?("0.24.5").should be_nil end it "should convert strings with leading 0 to integer if they are not octal" do Puppet::Parser::Scope.number?("0788").should == 788 end it "should convert strings of negative integers" do Puppet::Parser::Scope.number?("-0788").should == -788 end it "should return nil on malformed hexadecimal numbers" do Puppet::Parser::Scope.number?("0x89g").should be_nil end end describe "when using ephemeral variables" do it "should store the variable value" do # @scope.setvar("1", :value, :ephemeral => true) @scope.set_match_data({1 => :value}) @scope["1"].should == :value end it "should remove the variable value when unset_ephemeral_var(:all) is called" do # @scope.setvar("1", :value, :ephemeral => true) @scope.set_match_data({1 => :value}) @scope.stubs(:parent).returns(nil) @scope.unset_ephemeral_var(:all) @scope["1"].should be_nil end it "should not remove classic variables when unset_ephemeral_var(:all) is called" do @scope['myvar'] = :value1 @scope.set_match_data({1 => :value2}) @scope.stubs(:parent).returns(nil) @scope.unset_ephemeral_var(:all) @scope["myvar"].should == :value1 end it "should raise an error when setting numerical variable" do expect { @scope.setvar("1", :value3, :ephemeral => true) }.to raise_error(Puppet::ParseError, /Cannot assign to a numeric match result variable/) end describe "with more than one level" do it "should prefer latest ephemeral scopes" do @scope.set_match_data({0 => :earliest}) @scope.new_ephemeral @scope.set_match_data({0 => :latest}) @scope["0"].should == :latest end it "should be able to report the current level" do @scope.ephemeral_level.should == 1 @scope.new_ephemeral @scope.ephemeral_level.should == 2 end it "should not check presence of an ephemeral variable across multiple levels" do # This test was testing that scope actuallys screwed up - making values from earlier matches show as if they # where true for latest match - insanity ! @scope.new_ephemeral @scope.set_match_data({1 => :value1}) @scope.new_ephemeral @scope.set_match_data({0 => :value2}) @scope.new_ephemeral - @scope.include?("1").should be_false + @scope.include?("1").should be_falsey end it "should return false when an ephemeral variable doesn't exist in any ephemeral scope" do @scope.new_ephemeral @scope.set_match_data({1 => :value1}) @scope.new_ephemeral @scope.set_match_data({0 => :value2}) @scope.new_ephemeral - @scope.include?("2").should be_false + @scope.include?("2").should be_falsey end it "should not get ephemeral values from earlier scope when not in later" do @scope.set_match_data({1 => :value1}) @scope.new_ephemeral @scope.set_match_data({0 => :value2}) - @scope.include?("1").should be_false + @scope.include?("1").should be_falsey end describe "when calling unset_ephemeral_var with a level" do it "should remove ephemeral scopes up to this level" do @scope.set_match_data({1 => :value1}) @scope.new_ephemeral @scope.set_match_data({1 => :value2}) level = @scope.ephemeral_level() @scope.new_ephemeral @scope.set_match_data({1 => :value3}) @scope.unset_ephemeral_var(level) @scope["1"].should == :value2 end end end end context "when using ephemeral as local scope" do it "should store all variables in local scope" do @scope.new_ephemeral true @scope.setvar("apple", :fruit) @scope["apple"].should == :fruit end it "should remove all local scope variables on unset" do @scope.new_ephemeral true @scope.setvar("apple", :fruit) @scope["apple"].should == :fruit @scope.unset_ephemeral_var @scope["apple"].should == nil end it "should be created from a hash" do @scope.ephemeral_from({ "apple" => :fruit, "strawberry" => :berry}) @scope["apple"].should == :fruit @scope["strawberry"].should == :berry end end describe "when setting ephemeral vars from matches" do before :each do @match = stub 'match', :is_a? => true @match.stubs(:[]).with(0).returns("this is a string") @match.stubs(:captures).returns([]) @scope.stubs(:setvar) end it "should accept only MatchData" do expect { @scope.ephemeral_from("match") }.to raise_error(ArgumentError, /Invalid regex match data/) end it "should set $0 with the full match" do # This is an internal impl detail test @scope.expects(:new_match_scope).with { |*arg| arg[0][0] == "this is a string" } @scope.ephemeral_from(@match) end it "should set every capture as ephemeral var" do # This is an internal impl detail test @match.stubs(:[]).with(1).returns(:capture1) @match.stubs(:[]).with(2).returns(:capture2) @scope.expects(:new_match_scope).with { |*arg| arg[0][1] == :capture1 && arg[0][2] == :capture2 } @scope.ephemeral_from(@match) end it "should shadow previous match variables" do # This is an internal impl detail test @match.stubs(:[]).with(1).returns(:capture1) @match.stubs(:[]).with(2).returns(:capture2) @match2 = stub 'match', :is_a? => true @match2.stubs(:[]).with(1).returns(:capture2_1) @match2.stubs(:[]).with(2).returns(nil) @scope.ephemeral_from(@match) @scope.ephemeral_from(@match2) @scope.lookupvar('2').should == nil end it "should create a new ephemeral level" do level_before = @scope.ephemeral_level @scope.ephemeral_from(@match) expect(level_before < @scope.ephemeral_level) end end describe "when managing defaults" do it "should be able to set and lookup defaults" do param = Puppet::Parser::Resource::Param.new(:name => :myparam, :value => "myvalue", :source => stub("source")) @scope.define_settings(:mytype, param) @scope.lookupdefaults(:mytype).should == {:myparam => param} end it "should fail if a default is already defined and a new default is being defined" do param = Puppet::Parser::Resource::Param.new(:name => :myparam, :value => "myvalue", :source => stub("source")) @scope.define_settings(:mytype, param) expect { @scope.define_settings(:mytype, param) }.to raise_error(Puppet::ParseError, /Default already defined .* cannot redefine/) end it "should return multiple defaults at once" do param1 = Puppet::Parser::Resource::Param.new(:name => :myparam, :value => "myvalue", :source => stub("source")) @scope.define_settings(:mytype, param1) param2 = Puppet::Parser::Resource::Param.new(:name => :other, :value => "myvalue", :source => stub("source")) @scope.define_settings(:mytype, param2) @scope.lookupdefaults(:mytype).should == {:myparam => param1, :other => param2} end it "should look up defaults defined in parent scopes" do param1 = Puppet::Parser::Resource::Param.new(:name => :myparam, :value => "myvalue", :source => stub("source")) @scope.define_settings(:mytype, param1) child_scope = @scope.newscope param2 = Puppet::Parser::Resource::Param.new(:name => :other, :value => "myvalue", :source => stub("source")) child_scope.define_settings(:mytype, param2) child_scope.lookupdefaults(:mytype).should == {:myparam => param1, :other => param2} end end context "#true?" do { "a string" => true, "true" => true, "false" => true, true => true, "" => false, :undef => false, nil => false }.each do |input, output| it "should treat #{input.inspect} as #{output}" do Puppet::Parser::Scope.true?(input).should == output end end end context "when producing a hash of all variables (as used in templates)" do it "should contain all defined variables in the scope" do @scope.setvar("orange", :tangerine) @scope.setvar("pear", :green) @scope.to_hash.should == {'orange' => :tangerine, 'pear' => :green } end it "should contain variables in all local scopes (#21508)" do @scope.new_ephemeral true @scope.setvar("orange", :tangerine) @scope.setvar("pear", :green) @scope.new_ephemeral true @scope.setvar("apple", :red) @scope.to_hash.should == {'orange' => :tangerine, 'pear' => :green, 'apple' => :red } end it "should contain all defined variables in the scope and all local scopes" do @scope.setvar("orange", :tangerine) @scope.setvar("pear", :green) @scope.new_ephemeral true @scope.setvar("apple", :red) @scope.to_hash.should == {'orange' => :tangerine, 'pear' => :green, 'apple' => :red } end it "should not contain varaibles in match scopes (non local emphemeral)" do @scope.new_ephemeral true @scope.setvar("orange", :tangerine) @scope.setvar("pear", :green) @scope.ephemeral_from(/(f)(o)(o)/.match('foo')) @scope.to_hash.should == {'orange' => :tangerine, 'pear' => :green } end it "should delete values that are :undef in inner scope" do @scope.new_ephemeral true @scope.setvar("orange", :tangerine) @scope.setvar("pear", :green) @scope.new_ephemeral true @scope.setvar("apple", :red) @scope.setvar("orange", :undef) @scope.to_hash.should == {'pear' => :green, 'apple' => :red } end end end diff --git a/spec/unit/pops/loaders/loaders_spec.rb b/spec/unit/pops/loaders/loaders_spec.rb index 831236698..76958c714 100644 --- a/spec/unit/pops/loaders/loaders_spec.rb +++ b/spec/unit/pops/loaders/loaders_spec.rb @@ -1,125 +1,125 @@ require 'spec_helper' require 'puppet_spec/files' require 'puppet/pops' require 'puppet/loaders' describe 'loader helper classes' do it 'NamedEntry holds values and is frozen' do ne = Puppet::Pops::Loader::Loader::NamedEntry.new('name', 'value', 'origin') - expect(ne.frozen?).to be_true + expect(ne.frozen?).to be_truthy expect(ne.typed_name).to eql('name') expect(ne.origin).to eq('origin') expect(ne.value).to eq('value') end it 'TypedName holds values and is frozen' do tn = Puppet::Pops::Loader::Loader::TypedName.new(:function, '::foo::bar') - expect(tn.frozen?).to be_true + expect(tn.frozen?).to be_truthy expect(tn.type).to eq(:function) expect(tn.name_parts).to eq(['foo', 'bar']) expect(tn.name).to eq('foo::bar') - expect(tn.qualified).to be_true + expect(tn.qualified).to be_truthy end end describe 'loaders' do include PuppetSpec::Files let(:module_without_metadata) { File.join(config_dir('wo_metadata_module'), 'modules') } let(:module_with_metadata) { File.join(config_dir('single_module'), 'modules') } let(:dependent_modules_with_metadata) { config_dir('dependent_modules_with_metadata') } let(:empty_test_env) { environment_for() } # Loaders caches the puppet_system_loader, must reset between tests before(:each) { Puppet::Pops::Loaders.clear() } it 'creates a puppet_system loader' do loaders = Puppet::Pops::Loaders.new(empty_test_env) expect(loaders.puppet_system_loader()).to be_a(Puppet::Pops::Loader::ModuleLoaders::FileBased) end it 'creates an environment loader' do loaders = Puppet::Pops::Loaders.new(empty_test_env) expect(loaders.public_environment_loader()).to be_a(Puppet::Pops::Loader::SimpleEnvironmentLoader) expect(loaders.public_environment_loader().to_s).to eql("(SimpleEnvironmentLoader 'environment:*test*')") expect(loaders.private_environment_loader()).to be_a(Puppet::Pops::Loader::DependencyLoader) expect(loaders.private_environment_loader().to_s).to eql("(DependencyLoader 'environment' [])") end it 'can load a function using a qualified or unqualified name from a module with metadata' do loaders = Puppet::Pops::Loaders.new(environment_for(module_with_metadata)) modulea_loader = loaders.public_loader_for_module('modulea') unqualified_function = modulea_loader.load_typed(typed_name(:function, 'rb_func_a')).value qualified_function = modulea_loader.load_typed(typed_name(:function, 'modulea::rb_func_a')).value expect(unqualified_function).to be_a(Puppet::Functions::Function) expect(qualified_function).to be_a(Puppet::Functions::Function) expect(unqualified_function.class.name).to eq('rb_func_a') expect(qualified_function.class.name).to eq('modulea::rb_func_a') end it 'can load a function with a qualified name from module without metadata' do loaders = Puppet::Pops::Loaders.new(environment_for(module_without_metadata)) moduleb_loader = loaders.public_loader_for_module('moduleb') function = moduleb_loader.load_typed(typed_name(:function, 'moduleb::rb_func_b')).value expect(function).to be_a(Puppet::Functions::Function) expect(function.class.name).to eq('moduleb::rb_func_b') end it 'cannot load an unqualified function from a module without metadata' do loaders = Puppet::Pops::Loaders.new(environment_for(module_without_metadata)) moduleb_loader = loaders.public_loader_for_module('moduleb') expect(moduleb_loader.load_typed(typed_name(:function, 'rb_func_b'))).to be_nil end it 'makes all other modules visible to a module without metadata' do env = environment_for(module_with_metadata, module_without_metadata) loaders = Puppet::Pops::Loaders.new(env) moduleb_loader = loaders.private_loader_for_module('moduleb') function = moduleb_loader.load_typed(typed_name(:function, 'moduleb::rb_func_b')).value expect(function.call({})).to eql("I am modulea::rb_func_a() + I am moduleb::rb_func_b()") end it 'makes dependent modules visible to a module with metadata' do env = environment_for(dependent_modules_with_metadata) loaders = Puppet::Pops::Loaders.new(env) moduleb_loader = loaders.private_loader_for_module('user') function = moduleb_loader.load_typed(typed_name(:function, 'user::caller')).value expect(function.call({})).to eql("usee::callee() was told 'passed value' + I am user::caller()") end it 'can load a function more than once from modules' do env = environment_for(dependent_modules_with_metadata) loaders = Puppet::Pops::Loaders.new(env) moduleb_loader = loaders.private_loader_for_module('user') function = moduleb_loader.load_typed(typed_name(:function, 'user::caller')).value expect(function.call({})).to eql("usee::callee() was told 'passed value' + I am user::caller()") function = moduleb_loader.load_typed(typed_name(:function, 'user::caller')).value expect(function.call({})).to eql("usee::callee() was told 'passed value' + I am user::caller()") end def environment_for(*module_paths) Puppet::Node::Environment.create(:'*test*', module_paths, '') end def typed_name(type, name) Puppet::Pops::Loader::Loader::TypedName.new(type, name) end def config_dir(config_name) my_fixture(config_name) end end diff --git a/spec/unit/pops/types/type_calculator_spec.rb b/spec/unit/pops/types/type_calculator_spec.rb index b1dd286de..6e674e266 100644 --- a/spec/unit/pops/types/type_calculator_spec.rb +++ b/spec/unit/pops/types/type_calculator_spec.rb @@ -1,1877 +1,1877 @@ require 'spec_helper' require 'puppet/pops' describe 'The type calculator' do let(:calculator) { Puppet::Pops::Types::TypeCalculator.new() } def range_t(from, to) t = Puppet::Pops::Types::PIntegerType.new t.from = from t.to = to t end def constrained_t(t, from, to) Puppet::Pops::Types::TypeFactory.constrain_size(t, from, to) end def pattern_t(*patterns) Puppet::Pops::Types::TypeFactory.pattern(*patterns) end def regexp_t(pattern) Puppet::Pops::Types::TypeFactory.regexp(pattern) end def string_t(*strings) Puppet::Pops::Types::TypeFactory.string(*strings) end def callable_t(*params) Puppet::Pops::Types::TypeFactory.callable(*params) end def all_callables_t(*params) Puppet::Pops::Types::TypeFactory.all_callables() end def with_block_t(callable_t, *params) Puppet::Pops::Types::TypeFactory.with_block(callable_t, *params) end def with_optional_block_t(callable_t, *params) Puppet::Pops::Types::TypeFactory.with_optional_block(callable_t, *params) end def enum_t(*strings) Puppet::Pops::Types::TypeFactory.enum(*strings) end def variant_t(*types) Puppet::Pops::Types::TypeFactory.variant(*types) end def integer_t() Puppet::Pops::Types::TypeFactory.integer() end def array_t(t) Puppet::Pops::Types::TypeFactory.array_of(t) end def hash_t(k,v) Puppet::Pops::Types::TypeFactory.hash_of(v, k) end def data_t() Puppet::Pops::Types::TypeFactory.data() end def factory() Puppet::Pops::Types::TypeFactory end def collection_t() Puppet::Pops::Types::TypeFactory.collection() end def tuple_t(*types) Puppet::Pops::Types::TypeFactory.tuple(*types) end def struct_t(type_hash) Puppet::Pops::Types::TypeFactory.struct(type_hash) end def object_t Puppet::Pops::Types::TypeFactory.any() end def unit_t # Cannot be created via factory, the type is private to the type system Puppet::Pops::Types::PUnitType.new end def types Puppet::Pops::Types end shared_context "types_setup" do # Do not include the special type Unit in this list def all_types [ Puppet::Pops::Types::PAnyType, Puppet::Pops::Types::PNilType, Puppet::Pops::Types::PDataType, Puppet::Pops::Types::PScalarType, Puppet::Pops::Types::PStringType, Puppet::Pops::Types::PNumericType, Puppet::Pops::Types::PIntegerType, Puppet::Pops::Types::PFloatType, Puppet::Pops::Types::PRegexpType, Puppet::Pops::Types::PBooleanType, Puppet::Pops::Types::PCollectionType, Puppet::Pops::Types::PArrayType, Puppet::Pops::Types::PHashType, Puppet::Pops::Types::PRuntimeType, Puppet::Pops::Types::PHostClassType, Puppet::Pops::Types::PResourceType, Puppet::Pops::Types::PPatternType, Puppet::Pops::Types::PEnumType, Puppet::Pops::Types::PVariantType, Puppet::Pops::Types::PStructType, Puppet::Pops::Types::PTupleType, Puppet::Pops::Types::PCallableType, Puppet::Pops::Types::PType, Puppet::Pops::Types::POptionalType, Puppet::Pops::Types::PDefaultType, ] end def scalar_types # PVariantType is also scalar, if its types are all Scalar [ Puppet::Pops::Types::PScalarType, Puppet::Pops::Types::PStringType, Puppet::Pops::Types::PNumericType, Puppet::Pops::Types::PIntegerType, Puppet::Pops::Types::PFloatType, Puppet::Pops::Types::PRegexpType, Puppet::Pops::Types::PBooleanType, Puppet::Pops::Types::PPatternType, Puppet::Pops::Types::PEnumType, ] end def numeric_types # PVariantType is also numeric, if its types are all numeric [ Puppet::Pops::Types::PNumericType, Puppet::Pops::Types::PIntegerType, Puppet::Pops::Types::PFloatType, ] end def string_types # PVariantType is also string type, if its types are all compatible [ Puppet::Pops::Types::PStringType, Puppet::Pops::Types::PPatternType, Puppet::Pops::Types::PEnumType, ] end def collection_types # PVariantType is also string type, if its types are all compatible [ Puppet::Pops::Types::PCollectionType, Puppet::Pops::Types::PHashType, Puppet::Pops::Types::PArrayType, Puppet::Pops::Types::PStructType, Puppet::Pops::Types::PTupleType, ] end def data_compatible_types result = scalar_types result << Puppet::Pops::Types::PDataType result << array_t(types::PDataType.new) result << types::TypeFactory.hash_of_data result << Puppet::Pops::Types::PNilType tmp = tuple_t(types::PDataType.new) result << (tmp) tmp.size_type = range_t(0, nil) result end def type_from_class(c) c.is_a?(Class) ? c.new : c end end context 'when inferring ruby' do it 'fixnum translates to PIntegerType' do calculator.infer(1).class.should == Puppet::Pops::Types::PIntegerType end it 'large fixnum (or bignum depending on architecture) translates to PIntegerType' do calculator.infer(2**33).class.should == Puppet::Pops::Types::PIntegerType end it 'float translates to PFloatType' do calculator.infer(1.3).class.should == Puppet::Pops::Types::PFloatType end it 'string translates to PStringType' do calculator.infer('foo').class.should == Puppet::Pops::Types::PStringType end it 'inferred string type knows the string value' do t = calculator.infer('foo') t.class.should == Puppet::Pops::Types::PStringType t.values.should == ['foo'] end it 'boolean true translates to PBooleanType' do calculator.infer(true).class.should == Puppet::Pops::Types::PBooleanType end it 'boolean false translates to PBooleanType' do calculator.infer(false).class.should == Puppet::Pops::Types::PBooleanType end it 'regexp translates to PRegexpType' do calculator.infer(/^a regular expression$/).class.should == Puppet::Pops::Types::PRegexpType end it 'nil translates to PNilType' do calculator.infer(nil).class.should == Puppet::Pops::Types::PNilType end it ':undef translates to PRuntimeType' do calculator.infer(:undef).class.should == Puppet::Pops::Types::PRuntimeType end it 'an instance of class Foo translates to PRuntimeType[ruby, Foo]' do class Foo end t = calculator.infer(Foo.new) t.class.should == Puppet::Pops::Types::PRuntimeType t.runtime.should == :ruby t.runtime_type_name.should == 'Foo' end context 'array' do it 'translates to PArrayType' do calculator.infer([1,2]).class.should == Puppet::Pops::Types::PArrayType end it 'with fixnum values translates to PArrayType[PIntegerType]' do calculator.infer([1,2]).element_type.class.should == Puppet::Pops::Types::PIntegerType end it 'with 32 and 64 bit integer values translates to PArrayType[PIntegerType]' do calculator.infer([1,2**33]).element_type.class.should == Puppet::Pops::Types::PIntegerType end it 'Range of integer values are computed' do t = calculator.infer([-3,0,42]).element_type t.class.should == Puppet::Pops::Types::PIntegerType t.from.should == -3 t.to.should == 42 end it "Compound string values are computed" do t = calculator.infer(['a','b', 'c']).element_type t.class.should == Puppet::Pops::Types::PStringType t.values.should == ['a', 'b', 'c'] end it 'with fixnum and float values translates to PArrayType[PNumericType]' do calculator.infer([1,2.0]).element_type.class.should == Puppet::Pops::Types::PNumericType end it 'with fixnum and string values translates to PArrayType[PScalarType]' do calculator.infer([1,'two']).element_type.class.should == Puppet::Pops::Types::PScalarType end it 'with float and string values translates to PArrayType[PScalarType]' do calculator.infer([1.0,'two']).element_type.class.should == Puppet::Pops::Types::PScalarType end it 'with fixnum, float, and string values translates to PArrayType[PScalarType]' do calculator.infer([1, 2.0,'two']).element_type.class.should == Puppet::Pops::Types::PScalarType end it 'with fixnum and regexp values translates to PArrayType[PScalarType]' do calculator.infer([1, /two/]).element_type.class.should == Puppet::Pops::Types::PScalarType end it 'with string and regexp values translates to PArrayType[PScalarType]' do calculator.infer(['one', /two/]).element_type.class.should == Puppet::Pops::Types::PScalarType end it 'with string and symbol values translates to PArrayType[PAnyType]' do calculator.infer(['one', :two]).element_type.class.should == Puppet::Pops::Types::PAnyType end it 'with fixnum and nil values translates to PArrayType[PIntegerType]' do calculator.infer([1, nil]).element_type.class.should == Puppet::Pops::Types::PIntegerType end it 'with arrays of string values translates to PArrayType[PArrayType[PStringType]]' do et = calculator.infer([['first' 'array'], ['second','array']]) et.class.should == Puppet::Pops::Types::PArrayType et = et.element_type et.class.should == Puppet::Pops::Types::PArrayType et = et.element_type et.class.should == Puppet::Pops::Types::PStringType end it 'with array of string values and array of fixnums translates to PArrayType[PArrayType[PScalarType]]' do et = calculator.infer([['first' 'array'], [1,2]]) et.class.should == Puppet::Pops::Types::PArrayType et = et.element_type et.class.should == Puppet::Pops::Types::PArrayType et = et.element_type et.class.should == Puppet::Pops::Types::PScalarType end it 'with hashes of string values translates to PArrayType[PHashType[PStringType]]' do et = calculator.infer([{:first => 'first', :second => 'second' }, {:first => 'first', :second => 'second' }]) et.class.should == Puppet::Pops::Types::PArrayType et = et.element_type et.class.should == Puppet::Pops::Types::PHashType et = et.element_type et.class.should == Puppet::Pops::Types::PStringType end it 'with hash of string values and hash of fixnums translates to PArrayType[PHashType[PScalarType]]' do et = calculator.infer([{:first => 'first', :second => 'second' }, {:first => 1, :second => 2 }]) et.class.should == Puppet::Pops::Types::PArrayType et = et.element_type et.class.should == Puppet::Pops::Types::PHashType et = et.element_type et.class.should == Puppet::Pops::Types::PScalarType end end context 'hash' do it 'translates to PHashType' do calculator.infer({:first => 1, :second => 2}).class.should == Puppet::Pops::Types::PHashType end it 'with symbolic keys translates to PHashType[PRuntimeType[ruby, Symbol], value]' do k = calculator.infer({:first => 1, :second => 2}).key_type k.class.should == Puppet::Pops::Types::PRuntimeType k.runtime.should == :ruby k.runtime_type_name.should == 'Symbol' end it 'with string keys translates to PHashType[PStringType, value]' do calculator.infer({'first' => 1, 'second' => 2}).key_type.class.should == Puppet::Pops::Types::PStringType end it 'with fixnum values translates to PHashType[key, PIntegerType]' do calculator.infer({:first => 1, :second => 2}).element_type.class.should == Puppet::Pops::Types::PIntegerType end end end context 'patterns' do it "constructs a PPatternType" do t = pattern_t('a(b)c') t.class.should == Puppet::Pops::Types::PPatternType t.patterns.size.should == 1 t.patterns[0].class.should == Puppet::Pops::Types::PRegexpType t.patterns[0].pattern.should == 'a(b)c' t.patterns[0].regexp.match('abc')[1].should == 'b' end it "constructs a PStringType with multiple strings" do t = string_t('a', 'b', 'c', 'abc') t.values.should == ['a', 'b', 'c', 'abc'] end end # Deal with cases not covered by computing common type context 'when computing common type' do it 'computes given resource type commonality' do r1 = Puppet::Pops::Types::PResourceType.new() r1.type_name = 'File' r2 = Puppet::Pops::Types::PResourceType.new() r2.type_name = 'File' calculator.string(calculator.common_type(r1, r2)).should == "File" r2 = Puppet::Pops::Types::PResourceType.new() r2.type_name = 'File' r2.title = '/tmp/foo' calculator.string(calculator.common_type(r1, r2)).should == "File" r1 = Puppet::Pops::Types::PResourceType.new() r1.type_name = 'File' r1.title = '/tmp/foo' calculator.string(calculator.common_type(r1, r2)).should == "File['/tmp/foo']" r1 = Puppet::Pops::Types::PResourceType.new() r1.type_name = 'File' r1.title = '/tmp/bar' calculator.string(calculator.common_type(r1, r2)).should == "File" r2 = Puppet::Pops::Types::PResourceType.new() r2.type_name = 'Package' r2.title = 'apache' calculator.string(calculator.common_type(r1, r2)).should == "Resource" end it 'computes given hostclass type commonality' do r1 = Puppet::Pops::Types::PHostClassType.new() r1.class_name = 'foo' r2 = Puppet::Pops::Types::PHostClassType.new() r2.class_name = 'foo' calculator.string(calculator.common_type(r1, r2)).should == "Class[foo]" r2 = Puppet::Pops::Types::PHostClassType.new() r2.class_name = 'bar' calculator.string(calculator.common_type(r1, r2)).should == "Class" r2 = Puppet::Pops::Types::PHostClassType.new() calculator.string(calculator.common_type(r1, r2)).should == "Class" r1 = Puppet::Pops::Types::PHostClassType.new() calculator.string(calculator.common_type(r1, r2)).should == "Class" end it 'computes pattern commonality' do t1 = pattern_t('abc') t2 = pattern_t('xyz') common_t = calculator.common_type(t1,t2) common_t.class.should == Puppet::Pops::Types::PPatternType common_t.patterns.map { |pr| pr.pattern }.should == ['abc', 'xyz'] calculator.string(common_t).should == "Pattern[/abc/, /xyz/]" end it 'computes enum commonality to value set sum' do t1 = enum_t('a', 'b', 'c') t2 = enum_t('x', 'y', 'z') common_t = calculator.common_type(t1, t2) common_t.should == enum_t('a', 'b', 'c', 'x', 'y', 'z') end it 'computed variant commonality to type union where added types are not sub-types' do a_t1 = integer_t() a_t2 = enum_t('b') v_a = variant_t(a_t1, a_t2) b_t1 = enum_t('a') v_b = variant_t(b_t1) common_t = calculator.common_type(v_a, v_b) common_t.class.should == Puppet::Pops::Types::PVariantType Set.new(common_t.types).should == Set.new([a_t1, a_t2, b_t1]) end it 'computed variant commonality to type union where added types are sub-types' do a_t1 = integer_t() a_t2 = string_t() v_a = variant_t(a_t1, a_t2) b_t1 = enum_t('a') v_b = variant_t(b_t1) common_t = calculator.common_type(v_a, v_b) common_t.class.should == Puppet::Pops::Types::PVariantType Set.new(common_t.types).should == Set.new([a_t1, a_t2]) end context "of callables" do it 'incompatible instances => generic callable' do t1 = callable_t(String) t2 = callable_t(Integer) common_t = calculator.common_type(t1, t2) expect(common_t.class).to be(Puppet::Pops::Types::PCallableType) expect(common_t.param_types).to be_nil expect(common_t.block_type).to be_nil end it 'compatible instances => the most specific' do t1 = callable_t(String) scalar_t = Puppet::Pops::Types::PScalarType.new t2 = callable_t(scalar_t) common_t = calculator.common_type(t1, t2) expect(common_t.class).to be(Puppet::Pops::Types::PCallableType) expect(common_t.param_types.class).to be(Puppet::Pops::Types::PTupleType) expect(common_t.param_types.types).to eql([string_t]) expect(common_t.block_type).to be_nil end it 'block_type is included in the check (incompatible block)' do t1 = with_block_t(callable_t(String), String) t2 = with_block_t(callable_t(String), Integer) common_t = calculator.common_type(t1, t2) expect(common_t.class).to be(Puppet::Pops::Types::PCallableType) expect(common_t.param_types).to be_nil expect(common_t.block_type).to be_nil end it 'block_type is included in the check (compatible block)' do t1 = with_block_t(callable_t(String), String) scalar_t = Puppet::Pops::Types::PScalarType.new t2 = with_block_t(callable_t(String), scalar_t) common_t = calculator.common_type(t1, t2) expect(common_t.param_types.class).to be(Puppet::Pops::Types::PTupleType) expect(common_t.block_type).to eql(callable_t(scalar_t)) end end end context 'computes assignability' do include_context "types_setup" context 'for Unit, such that' do it 'all types are assignable to Unit' do t = Puppet::Pops::Types::PUnitType.new() all_types.each { |t2| t2.new.should be_assignable_to(t) } end it 'Unit is assignable to all other types' do t = Puppet::Pops::Types::PUnitType.new() all_types.each { |t2| t.should be_assignable_to(t2.new) } end it 'Unit is assignable to Unit' do t = Puppet::Pops::Types::PUnitType.new() t2 = Puppet::Pops::Types::PUnitType.new() t.should be_assignable_to(t2) end end context "for Any, such that" do it 'all types are assignable to Any' do t = Puppet::Pops::Types::PAnyType.new() all_types.each { |t2| t2.new.should be_assignable_to(t) } end it 'Any is not assignable to anything but Any' do tested_types = all_types() - [Puppet::Pops::Types::PAnyType] t = Puppet::Pops::Types::PAnyType.new() tested_types.each { |t2| t.should_not be_assignable_to(t2.new) } end end context "for Data, such that" do it 'all scalars + array and hash are assignable to Data' do t = Puppet::Pops::Types::PDataType.new() data_compatible_types.each { |t2| type_from_class(t2).should be_assignable_to(t) } end it 'a Variant of scalar, hash, or array is assignable to Data' do t = Puppet::Pops::Types::PDataType.new() data_compatible_types.each { |t2| variant_t(type_from_class(t2)).should be_assignable_to(t) } end it 'Data is not assignable to any of its subtypes' do t = Puppet::Pops::Types::PDataType.new() types_to_test = data_compatible_types- [Puppet::Pops::Types::PDataType] types_to_test.each {|t2| t.should_not be_assignable_to(type_from_class(t2)) } end it 'Data is not assignable to a Variant of Data subtype' do t = Puppet::Pops::Types::PDataType.new() types_to_test = data_compatible_types- [Puppet::Pops::Types::PDataType] types_to_test.each { |t2| t.should_not be_assignable_to(variant_t(type_from_class(t2))) } end it 'Data is not assignable to any disjunct type' do tested_types = all_types - [Puppet::Pops::Types::PAnyType, Puppet::Pops::Types::PDataType] - scalar_types t = Puppet::Pops::Types::PDataType.new() tested_types.each {|t2| t.should_not be_assignable_to(t2.new) } end end context 'for Variant, such that' do it 'it is assignable to a type if all contained types are assignable to that type' do v = variant_t(range_t(10, 12),range_t(14, 20)) v.should be_assignable_to(integer_t) v.should be_assignable_to(range_t(10, 20)) # test that both types are assignable to one of the variants OK v.should be_assignable_to(variant_t(range_t(10, 20), range_t(30, 40))) # test where each type is assignable to different types in a variant is OK v.should be_assignable_to(variant_t(range_t(10, 13), range_t(14, 40))) # not acceptable v.should_not be_assignable_to(range_t(0, 4)) v.should_not be_assignable_to(string_t) end end context "for Scalar, such that" do it "all scalars are assignable to Scalar" do t = Puppet::Pops::Types::PScalarType.new() scalar_types.each {|t2| t2.new.should be_assignable_to(t) } end it 'Scalar is not assignable to any of its subtypes' do t = Puppet::Pops::Types::PScalarType.new() types_to_test = scalar_types - [Puppet::Pops::Types::PScalarType] types_to_test.each {|t2| t.should_not be_assignable_to(t2.new) } end it 'Scalar is not assignable to any disjunct type' do tested_types = all_types - [Puppet::Pops::Types::PAnyType, Puppet::Pops::Types::PDataType] - scalar_types t = Puppet::Pops::Types::PScalarType.new() tested_types.each {|t2| t.should_not be_assignable_to(t2.new) } end end context "for Numeric, such that" do it "all numerics are assignable to Numeric" do t = Puppet::Pops::Types::PNumericType.new() numeric_types.each {|t2| t2.new.should be_assignable_to(t) } end it 'Numeric is not assignable to any of its subtypes' do t = Puppet::Pops::Types::PNumericType.new() types_to_test = numeric_types - [Puppet::Pops::Types::PNumericType] types_to_test.each {|t2| t.should_not be_assignable_to(t2.new) } end it 'Numeric is not assignable to any disjunct type' do tested_types = all_types - [ Puppet::Pops::Types::PAnyType, Puppet::Pops::Types::PDataType, Puppet::Pops::Types::PScalarType, ] - numeric_types t = Puppet::Pops::Types::PNumericType.new() tested_types.each {|t2| t.should_not be_assignable_to(t2.new) } end end context "for Collection, such that" do it "all collections are assignable to Collection" do t = Puppet::Pops::Types::PCollectionType.new() collection_types.each {|t2| t2.new.should be_assignable_to(t) } end it 'Collection is not assignable to any of its subtypes' do t = Puppet::Pops::Types::PCollectionType.new() types_to_test = collection_types - [Puppet::Pops::Types::PCollectionType] types_to_test.each {|t2| t.should_not be_assignable_to(t2.new) } end it 'Collection is not assignable to any disjunct type' do tested_types = all_types - [Puppet::Pops::Types::PAnyType] - collection_types t = Puppet::Pops::Types::PCollectionType.new() tested_types.each {|t2| t.should_not be_assignable_to(t2.new) } end end context "for Array, such that" do it "Array is not assignable to non Array based Collection type" do t = Puppet::Pops::Types::PArrayType.new() tested_types = collection_types - [ Puppet::Pops::Types::PCollectionType, Puppet::Pops::Types::PArrayType, Puppet::Pops::Types::PTupleType] tested_types.each {|t2| t.should_not be_assignable_to(t2.new) } end it 'Array is not assignable to any disjunct type' do tested_types = all_types - [ Puppet::Pops::Types::PAnyType, Puppet::Pops::Types::PDataType] - collection_types t = Puppet::Pops::Types::PArrayType.new() tested_types.each {|t2| t.should_not be_assignable_to(t2.new) } end end context "for Hash, such that" do it "Hash is not assignable to any other Collection type" do t = Puppet::Pops::Types::PHashType.new() tested_types = collection_types - [ Puppet::Pops::Types::PCollectionType, Puppet::Pops::Types::PStructType, Puppet::Pops::Types::PHashType] tested_types.each {|t2| t.should_not be_assignable_to(t2.new) } end it 'Hash is not assignable to any disjunct type' do tested_types = all_types - [ Puppet::Pops::Types::PAnyType, Puppet::Pops::Types::PDataType] - collection_types t = Puppet::Pops::Types::PHashType.new() tested_types.each {|t2| t.should_not be_assignable_to(t2.new) } end end context "for Tuple, such that" do it "Tuple is not assignable to any other non Array based Collection type" do t = Puppet::Pops::Types::PTupleType.new() tested_types = collection_types - [ Puppet::Pops::Types::PCollectionType, Puppet::Pops::Types::PTupleType, Puppet::Pops::Types::PArrayType] tested_types.each {|t2| t.should_not be_assignable_to(t2.new) } end it 'Tuple is not assignable to any disjunct type' do tested_types = all_types - [ Puppet::Pops::Types::PAnyType, Puppet::Pops::Types::PDataType] - collection_types t = Puppet::Pops::Types::PTupleType.new() tested_types.each {|t2| t.should_not be_assignable_to(t2.new) } end end context "for Struct, such that" do it "Struct is not assignable to any other non Hashed based Collection type" do t = Puppet::Pops::Types::PStructType.new() tested_types = collection_types - [ Puppet::Pops::Types::PCollectionType, Puppet::Pops::Types::PStructType, Puppet::Pops::Types::PHashType] tested_types.each {|t2| t.should_not be_assignable_to(t2.new) } end it 'Struct is not assignable to any disjunct type' do tested_types = all_types - [ Puppet::Pops::Types::PAnyType, Puppet::Pops::Types::PDataType] - collection_types t = Puppet::Pops::Types::PStructType.new() tested_types.each {|t2| t.should_not be_assignable_to(t2.new) } end end context "for Callable, such that" do it "Callable is not assignable to any disjunct type" do t = Puppet::Pops::Types::PCallableType.new() tested_types = all_types - [ Puppet::Pops::Types::PCallableType, Puppet::Pops::Types::PAnyType] tested_types.each {|t2| t.should_not be_assignable_to(t2.new) } end end it 'should recognize mapped ruby types' do { Integer => Puppet::Pops::Types::PIntegerType.new, Fixnum => Puppet::Pops::Types::PIntegerType.new, Bignum => Puppet::Pops::Types::PIntegerType.new, Float => Puppet::Pops::Types::PFloatType.new, Numeric => Puppet::Pops::Types::PNumericType.new, NilClass => Puppet::Pops::Types::PNilType.new, TrueClass => Puppet::Pops::Types::PBooleanType.new, FalseClass => Puppet::Pops::Types::PBooleanType.new, String => Puppet::Pops::Types::PStringType.new, Regexp => Puppet::Pops::Types::PRegexpType.new, Regexp => Puppet::Pops::Types::PRegexpType.new, Array => Puppet::Pops::Types::TypeFactory.array_of_data(), Hash => Puppet::Pops::Types::TypeFactory.hash_of_data() }.each do |ruby_type, puppet_type | ruby_type.should be_assignable_to(puppet_type) end end context 'when dealing with integer ranges' do it 'should accept an equal range' do calculator.assignable?(range_t(2,5), range_t(2,5)).should == true end it 'should accept an equal reverse range' do calculator.assignable?(range_t(2,5), range_t(5,2)).should == true end it 'should accept a narrower range' do calculator.assignable?(range_t(2,10), range_t(3,5)).should == true end it 'should accept a narrower reverse range' do calculator.assignable?(range_t(2,10), range_t(5,3)).should == true end it 'should reject a wider range' do calculator.assignable?(range_t(3,5), range_t(2,10)).should == false end it 'should reject a wider reverse range' do calculator.assignable?(range_t(3,5), range_t(10,2)).should == false end it 'should reject a partially overlapping range' do calculator.assignable?(range_t(3,5), range_t(2,4)).should == false calculator.assignable?(range_t(3,5), range_t(4,6)).should == false end it 'should reject a partially overlapping reverse range' do calculator.assignable?(range_t(3,5), range_t(4,2)).should == false calculator.assignable?(range_t(3,5), range_t(6,4)).should == false end end context 'when dealing with patterns' do it 'should accept a string matching a pattern' do p_t = pattern_t('abc') p_s = string_t('XabcY') calculator.assignable?(p_t, p_s).should == true end it 'should accept a regexp matching a pattern' do p_t = pattern_t(/abc/) p_s = string_t('XabcY') calculator.assignable?(p_t, p_s).should == true end it 'should accept a pattern matching a pattern' do p_t = pattern_t(pattern_t('abc')) p_s = string_t('XabcY') calculator.assignable?(p_t, p_s).should == true end it 'should accept a regexp matching a pattern' do p_t = pattern_t(regexp_t('abc')) p_s = string_t('XabcY') calculator.assignable?(p_t, p_s).should == true end it 'should accept a string matching all patterns' do p_t = pattern_t('abc', 'ab', 'c') p_s = string_t('XabcY') calculator.assignable?(p_t, p_s).should == true end it 'should accept multiple strings if they all match any patterns' do p_t = pattern_t('X', 'Y', 'abc') p_s = string_t('Xa', 'aY', 'abc') calculator.assignable?(p_t, p_s).should == true end it 'should reject a string not matching any patterns' do p_t = pattern_t('abc', 'ab', 'c') p_s = string_t('XqqqY') calculator.assignable?(p_t, p_s).should == false end it 'should reject multiple strings if not all match any patterns' do p_t = pattern_t('abc', 'ab', 'c', 'q') p_s = string_t('X', 'Y', 'Z') calculator.assignable?(p_t, p_s).should == false end it 'should accept enum matching patterns as instanceof' do enum = enum_t('XS', 'S', 'M', 'L' 'XL', 'XXL') pattern = pattern_t('S', 'M', 'L') calculator.assignable?(pattern, enum).should == true end it 'pattern should accept a variant where all variants are acceptable' do pattern = pattern_t(/^\w+$/) calculator.assignable?(pattern, variant_t(string_t('a'), string_t('b'))).should == true end it 'pattern representing all patterns should accept any pattern' do calculator.assignable?(pattern_t(), pattern_t('a')).should == true calculator.assignable?(pattern_t(), pattern_t()).should == true end it 'pattern representing all patterns should accept any enum' do calculator.assignable?(pattern_t(), enum_t('a')).should == true calculator.assignable?(pattern_t(), enum_t()).should == true end it 'pattern representing all patterns should accept any string' do calculator.assignable?(pattern_t(), string_t('a')).should == true calculator.assignable?(pattern_t(), string_t()).should == true end end context 'when dealing with enums' do it 'should accept a string with matching content' do calculator.assignable?(enum_t('a', 'b'), string_t('a')).should == true calculator.assignable?(enum_t('a', 'b'), string_t('b')).should == true calculator.assignable?(enum_t('a', 'b'), string_t('c')).should == false end it 'should accept an enum with matching enum' do calculator.assignable?(enum_t('a', 'b'), enum_t('a', 'b')).should == true calculator.assignable?(enum_t('a', 'b'), enum_t('a')).should == true calculator.assignable?(enum_t('a', 'b'), enum_t('c')).should == false end it 'non parameterized enum accepts any other enum but not the reverse' do calculator.assignable?(enum_t(), enum_t('a')).should == true calculator.assignable?(enum_t('a'), enum_t()).should == false end it 'enum should accept a variant where all variants are acceptable' do enum = enum_t('a', 'b') calculator.assignable?(enum, variant_t(string_t('a'), string_t('b'))).should == true end end context 'when dealing with string and enum combinations' do it 'should accept assigning any enum to unrestricted string' do calculator.assignable?(string_t(), enum_t('blue')).should == true calculator.assignable?(string_t(), enum_t('blue', 'red')).should == true end it 'should not accept assigning longer enum value to size restricted string' do calculator.assignable?(constrained_t(string_t(),2,2), enum_t('a','blue')).should == false end it 'should accept assigning any string to empty enum' do calculator.assignable?(enum_t(), string_t()).should == true end it 'should accept assigning empty enum to any string' do calculator.assignable?(string_t(), enum_t()).should == true end it 'should not accept assigning empty enum to size constrained string' do calculator.assignable?(constrained_t(string_t(),2,2), enum_t()).should == false end end context 'when dealing with string/pattern/enum combinations' do it 'any string is equal to any enum is equal to any pattern' do calculator.assignable?(string_t(), enum_t()).should == true calculator.assignable?(string_t(), pattern_t()).should == true calculator.assignable?(enum_t(), string_t()).should == true calculator.assignable?(enum_t(), pattern_t()).should == true calculator.assignable?(pattern_t(), string_t()).should == true calculator.assignable?(pattern_t(), enum_t()).should == true end end context 'when dealing with tuples' do it 'matches empty tuples' do tuple1 = tuple_t() tuple2 = tuple_t() calculator.assignable?(tuple1, tuple2).should == true calculator.assignable?(tuple2, tuple1).should == true end it 'accepts an empty tuple as assignable to a tuple with a min size of 0' do tuple1 = tuple_t(Object) factory.constrain_size(tuple1, 0, :default) tuple2 = tuple_t() calculator.assignable?(tuple1, tuple2).should == true calculator.assignable?(tuple2, tuple1).should == false end it 'should accept matching tuples' do tuple1 = tuple_t(1,2) tuple2 = tuple_t(Integer,Integer) calculator.assignable?(tuple1, tuple2).should == true calculator.assignable?(tuple2, tuple1).should == true end it 'should accept matching tuples where one is more general than the other' do tuple1 = tuple_t(1,2) tuple2 = tuple_t(Numeric,Numeric) calculator.assignable?(tuple1, tuple2).should == false calculator.assignable?(tuple2, tuple1).should == true end it 'should accept ranged tuples' do tuple1 = tuple_t(1) factory.constrain_size(tuple1, 5, 5) tuple2 = tuple_t(Integer,Integer, Integer, Integer, Integer) calculator.assignable?(tuple1, tuple2).should == true calculator.assignable?(tuple2, tuple1).should == true end it 'should reject ranged tuples when ranges does not match' do tuple1 = tuple_t(1) factory.constrain_size(tuple1, 4, 5) tuple2 = tuple_t(Integer,Integer, Integer, Integer, Integer) calculator.assignable?(tuple1, tuple2).should == true calculator.assignable?(tuple2, tuple1).should == false end it 'should reject ranged tuples when ranges does not match (using infinite upper bound)' do tuple1 = tuple_t(1) factory.constrain_size(tuple1, 4, :default) tuple2 = tuple_t(Integer,Integer, Integer, Integer, Integer) calculator.assignable?(tuple1, tuple2).should == true calculator.assignable?(tuple2, tuple1).should == false end it 'should accept matching tuples with optional entries by repeating last' do tuple1 = tuple_t(1,2) factory.constrain_size(tuple1, 0, :default) tuple2 = tuple_t(Numeric,Numeric) factory.constrain_size(tuple2, 0, :default) calculator.assignable?(tuple1, tuple2).should == false calculator.assignable?(tuple2, tuple1).should == true end it 'should accept matching tuples with optional entries' do tuple1 = tuple_t(Integer, Integer, String) factory.constrain_size(tuple1, 1, 3) array2 = factory.constrain_size(array_t(Integer),2,2) calculator.assignable?(tuple1, array2).should == true factory.constrain_size(tuple1, 3, 3) calculator.assignable?(tuple1, array2).should == false end it 'should accept matching array' do tuple1 = tuple_t(1,2) array = array_t(Integer) factory.constrain_size(array, 2, 2) calculator.assignable?(tuple1, array).should == true calculator.assignable?(array, tuple1).should == true end it 'should accept empty array when tuple allows min of 0' do tuple1 = tuple_t(Integer) factory.constrain_size(tuple1, 0, 1) array = array_t(Integer) factory.constrain_size(array, 0, 0) calculator.assignable?(tuple1, array).should == true calculator.assignable?(array, tuple1).should == false end end context 'when dealing with structs' do it 'should accept matching structs' do struct1 = struct_t({'a'=>Integer, 'b'=>Integer}) struct2 = struct_t({'a'=>Integer, 'b'=>Integer}) calculator.assignable?(struct1, struct2).should == true calculator.assignable?(struct2, struct1).should == true end it 'should accept matching structs where one is more general than the other' do struct1 = struct_t({'a'=>Integer, 'b'=>Integer}) struct2 = struct_t({'a'=>Numeric, 'b'=>Numeric}) calculator.assignable?(struct1, struct2).should == false calculator.assignable?(struct2, struct1).should == true end it 'should accept matching hash' do struct1 = struct_t({'a'=>Integer, 'b'=>Integer}) non_empty_string = string_t() non_empty_string.size_type = range_t(1, nil) hsh = hash_t(non_empty_string, Integer) factory.constrain_size(hsh, 2, 2) calculator.assignable?(struct1, hsh).should == true calculator.assignable?(hsh, struct1).should == true end end it 'should recognize ruby type inheritance' do class Foo end class Bar < Foo end fooType = calculator.infer(Foo.new) barType = calculator.infer(Bar.new) calculator.assignable?(fooType, fooType).should == true calculator.assignable?(Foo, fooType).should == true calculator.assignable?(fooType, barType).should == true calculator.assignable?(Foo, barType).should == true calculator.assignable?(barType, fooType).should == false calculator.assignable?(Bar, fooType).should == false end it "should allow host class with same name" do hc1 = Puppet::Pops::Types::TypeFactory.host_class('the_name') hc2 = Puppet::Pops::Types::TypeFactory.host_class('the_name') calculator.assignable?(hc1, hc2).should == true end it "should allow host class with name assigned to hostclass without name" do hc1 = Puppet::Pops::Types::TypeFactory.host_class() hc2 = Puppet::Pops::Types::TypeFactory.host_class('the_name') calculator.assignable?(hc1, hc2).should == true end it "should reject host classes with different names" do hc1 = Puppet::Pops::Types::TypeFactory.host_class('the_name') hc2 = Puppet::Pops::Types::TypeFactory.host_class('another_name') calculator.assignable?(hc1, hc2).should == false end it "should reject host classes without name assigned to host class with name" do hc1 = Puppet::Pops::Types::TypeFactory.host_class('the_name') hc2 = Puppet::Pops::Types::TypeFactory.host_class() calculator.assignable?(hc1, hc2).should == false end it "should allow resource with same type_name and title" do r1 = Puppet::Pops::Types::TypeFactory.resource('file', 'foo') r2 = Puppet::Pops::Types::TypeFactory.resource('file', 'foo') calculator.assignable?(r1, r2).should == true end it "should allow more specific resource assignment" do r1 = Puppet::Pops::Types::TypeFactory.resource() r2 = Puppet::Pops::Types::TypeFactory.resource('file') calculator.assignable?(r1, r2).should == true r2 = Puppet::Pops::Types::TypeFactory.resource('file', '/tmp/foo') calculator.assignable?(r1, r2).should == true r1 = Puppet::Pops::Types::TypeFactory.resource('file') calculator.assignable?(r1, r2).should == true end it "should reject less specific resource assignment" do r1 = Puppet::Pops::Types::TypeFactory.resource('file', '/tmp/foo') r2 = Puppet::Pops::Types::TypeFactory.resource('file') calculator.assignable?(r1, r2).should == false r2 = Puppet::Pops::Types::TypeFactory.resource() calculator.assignable?(r1, r2).should == false end end context 'when testing if x is instance of type t' do include_context "types_setup" it 'should consider undef to be instance of Any, NilType, and optional' do calculator.instance?(Puppet::Pops::Types::PNilType.new(), nil).should == true calculator.instance?(Puppet::Pops::Types::PAnyType.new(), nil).should == true calculator.instance?(Puppet::Pops::Types::POptionalType.new(), nil).should == true end it 'all types should be (ruby) instance of PAnyType' do all_types.each do |t| t.new.is_a?(Puppet::Pops::Types::PAnyType).should == true end end it "should consider :undef to be instance of Runtime['ruby', 'Symbol]" do calculator.instance?(Puppet::Pops::Types::PRuntimeType.new(:runtime => :ruby, :runtime_type_name => 'Symbol'), :undef).should == true end it "should consider :undef to be instance of an Optional type" do calculator.instance?(Puppet::Pops::Types::POptionalType.new(), :undef).should == true end it 'should not consider undef to be an instance of any other type than Any, NilType and Data' do types_to_test = all_types - [ Puppet::Pops::Types::PAnyType, Puppet::Pops::Types::PNilType, Puppet::Pops::Types::PDataType, Puppet::Pops::Types::POptionalType, ] types_to_test.each {|t| calculator.instance?(t.new, nil).should == false } types_to_test.each {|t| calculator.instance?(t.new, :undef).should == false } end it 'should consider default to be instance of Default and Any' do calculator.instance?(Puppet::Pops::Types::PDefaultType.new(), :default).should == true calculator.instance?(Puppet::Pops::Types::PAnyType.new(), :default).should == true end it 'should not consider "default" to be an instance of anything but Default, and Any' do types_to_test = all_types - [ Puppet::Pops::Types::PAnyType, Puppet::Pops::Types::PDefaultType, ] types_to_test.each {|t| calculator.instance?(t.new, :default).should == false } end it 'should consider fixnum instanceof PIntegerType' do calculator.instance?(Puppet::Pops::Types::PIntegerType.new(), 1).should == true end it 'should consider fixnum instanceof Fixnum' do calculator.instance?(Fixnum, 1).should == true end it 'should consider integer in range' do range = range_t(0,10) calculator.instance?(range, 1).should == true calculator.instance?(range, 10).should == true calculator.instance?(range, -1).should == false calculator.instance?(range, 11).should == false end it 'should consider string in length range' do range = factory.constrain_size(string_t, 1,3) calculator.instance?(range, 'a').should == true calculator.instance?(range, 'abc').should == true calculator.instance?(range, '').should == false calculator.instance?(range, 'abcd').should == false end it 'should consider array in length range' do range = factory.constrain_size(array_t(integer_t), 1,3) calculator.instance?(range, [1]).should == true calculator.instance?(range, [1,2,3]).should == true calculator.instance?(range, []).should == false calculator.instance?(range, [1,2,3,4]).should == false end it 'should consider hash in length range' do range = factory.constrain_size(hash_t(integer_t, integer_t), 1,2) calculator.instance?(range, {1=>1}).should == true calculator.instance?(range, {1=>1, 2=>2}).should == true calculator.instance?(range, {}).should == false calculator.instance?(range, {1=>1, 2=>2, 3=>3}).should == false end it 'should consider collection in length range for array ' do range = factory.constrain_size(collection_t, 1,3) calculator.instance?(range, [1]).should == true calculator.instance?(range, [1,2,3]).should == true calculator.instance?(range, []).should == false calculator.instance?(range, [1,2,3,4]).should == false end it 'should consider collection in length range for hash' do range = factory.constrain_size(collection_t, 1,2) calculator.instance?(range, {1=>1}).should == true calculator.instance?(range, {1=>1, 2=>2}).should == true calculator.instance?(range, {}).should == false calculator.instance?(range, {1=>1, 2=>2, 3=>3}).should == false end it 'should consider string matching enum as instanceof' do enum = enum_t('XS', 'S', 'M', 'L', 'XL', '0') calculator.instance?(enum, 'XS').should == true calculator.instance?(enum, 'S').should == true calculator.instance?(enum, 'XXL').should == false calculator.instance?(enum, '').should == false calculator.instance?(enum, '0').should == true calculator.instance?(enum, 0).should == false end it 'should consider array[string] as instance of Array[Enum] when strings are instance of Enum' do enum = enum_t('XS', 'S', 'M', 'L', 'XL', '0') array = array_t(enum) calculator.instance?(array, ['XS', 'S', 'XL']).should == true calculator.instance?(array, ['XS', 'S', 'XXL']).should == false end it 'should consider array[mixed] as instance of Variant[mixed] when mixed types are listed in Variant' do enum = enum_t('XS', 'S', 'M', 'L', 'XL') sizes = range_t(30, 50) array = array_t(variant_t(enum, sizes)) calculator.instance?(array, ['XS', 'S', 30, 50]).should == true calculator.instance?(array, ['XS', 'S', 'XXL']).should == false calculator.instance?(array, ['XS', 'S', 29]).should == false end it 'should consider array[seq] as instance of Tuple[seq] when elements of seq are instance of' do tuple = tuple_t(Integer, String, Float) calculator.instance?(tuple, [1, 'a', 3.14]).should == true calculator.instance?(tuple, [1.2, 'a', 3.14]).should == false calculator.instance?(tuple, [1, 1, 3.14]).should == false calculator.instance?(tuple, [1, 'a', 1]).should == false end it 'should consider hash[cont] as instance of Struct[cont-t]' do struct = struct_t({'a'=>Integer, 'b'=>String, 'c'=>Float}) calculator.instance?(struct, {'a'=>1, 'b'=>'a', 'c'=>3.14}).should == true calculator.instance?(struct, {'a'=>1.2, 'b'=>'a', 'c'=>3.14}).should == false calculator.instance?(struct, {'a'=>1, 'b'=>1, 'c'=>3.14}).should == false calculator.instance?(struct, {'a'=>1, 'b'=>'a', 'c'=>1}).should == false end context 'and t is Data' do it 'undef should be considered instance of Data' do calculator.instance?(data_t, nil).should == true end it 'other symbols should not be considered instance of Data' do calculator.instance?(data_t, :love).should == false end it 'an empty array should be considered instance of Data' do calculator.instance?(data_t, []).should == true end it 'an empty hash should be considered instance of Data' do calculator.instance?(data_t, {}).should == true end it 'a hash with nil/undef data should be considered instance of Data' do calculator.instance?(data_t, {'a' => nil}).should == true end it 'a hash with nil/default key should not considered instance of Data' do calculator.instance?(data_t, {nil => 10}).should == false calculator.instance?(data_t, {:default => 10}).should == false end it 'an array with nil entries should be considered instance of Data' do calculator.instance?(data_t, [nil]).should == true end it 'an array with nil + data entries should be considered instance of Data' do calculator.instance?(data_t, [1, nil, 'a']).should == true end end context "and t is something Callable" do it 'a Closure should be considered a Callable' do factory = Puppet::Pops::Model::Factory params = [factory.PARAM('a')] the_block = factory.LAMBDA(params,factory.literal(42)) the_closure = Puppet::Pops::Evaluator::Closure.new(:fake_evaluator, the_block, :fake_scope) - expect(calculator.instance?(all_callables_t, the_closure)).to be_true - expect(calculator.instance?(callable_t(object_t), the_closure)).to be_true - expect(calculator.instance?(callable_t(object_t, object_t), the_closure)).to be_false + expect(calculator.instance?(all_callables_t, the_closure)).to be_truthy + expect(calculator.instance?(callable_t(object_t), the_closure)).to be_truthy + expect(calculator.instance?(callable_t(object_t, object_t), the_closure)).to be_falsey end it 'a Function instance should be considered a Callable' do fc = Puppet::Functions.create_function(:foo) do dispatch :foo do param 'String', :a end def foo(a) a end end f = fc.new(:closure_scope, :loader) # Any callable - expect(calculator.instance?(all_callables_t, f)).to be_true + expect(calculator.instance?(all_callables_t, f)).to be_truthy # Callable[String] - expect(calculator.instance?(callable_t(String), f)).to be_true + expect(calculator.instance?(callable_t(String), f)).to be_truthy end end end context 'when converting a ruby class' do it 'should yield \'PIntegerType\' for Integer, Fixnum, and Bignum' do [Integer,Fixnum,Bignum].each do |c| calculator.type(c).class.should == Puppet::Pops::Types::PIntegerType end end it 'should yield \'PFloatType\' for Float' do calculator.type(Float).class.should == Puppet::Pops::Types::PFloatType end it 'should yield \'PBooleanType\' for FalseClass and TrueClass' do [FalseClass,TrueClass].each do |c| calculator.type(c).class.should == Puppet::Pops::Types::PBooleanType end end it 'should yield \'PNilType\' for NilClass' do calculator.type(NilClass).class.should == Puppet::Pops::Types::PNilType end it 'should yield \'PStringType\' for String' do calculator.type(String).class.should == Puppet::Pops::Types::PStringType end it 'should yield \'PRegexpType\' for Regexp' do calculator.type(Regexp).class.should == Puppet::Pops::Types::PRegexpType end it 'should yield \'PArrayType[PDataType]\' for Array' do t = calculator.type(Array) t.class.should == Puppet::Pops::Types::PArrayType t.element_type.class.should == Puppet::Pops::Types::PDataType end it 'should yield \'PHashType[PScalarType,PDataType]\' for Hash' do t = calculator.type(Hash) t.class.should == Puppet::Pops::Types::PHashType t.key_type.class.should == Puppet::Pops::Types::PScalarType t.element_type.class.should == Puppet::Pops::Types::PDataType end end context 'when representing the type as string' do it 'should yield \'Type\' for PType' do calculator.string(Puppet::Pops::Types::PType.new()).should == 'Type' end it 'should yield \'Object\' for PAnyType' do calculator.string(Puppet::Pops::Types::PAnyType.new()).should == 'Any' end it 'should yield \'Scalar\' for PScalarType' do calculator.string(Puppet::Pops::Types::PScalarType.new()).should == 'Scalar' end it 'should yield \'Boolean\' for PBooleanType' do calculator.string(Puppet::Pops::Types::PBooleanType.new()).should == 'Boolean' end it 'should yield \'Data\' for PDataType' do calculator.string(Puppet::Pops::Types::PDataType.new()).should == 'Data' end it 'should yield \'Numeric\' for PNumericType' do calculator.string(Puppet::Pops::Types::PNumericType.new()).should == 'Numeric' end it 'should yield \'Integer\' and from/to for PIntegerType' do int_T = Puppet::Pops::Types::PIntegerType calculator.string(int_T.new()).should == 'Integer' int = int_T.new() int.from = 1 int.to = 1 calculator.string(int).should == 'Integer[1, 1]' int = int_T.new() int.from = 1 int.to = 2 calculator.string(int).should == 'Integer[1, 2]' int = int_T.new() int.from = nil int.to = 2 calculator.string(int).should == 'Integer[default, 2]' int = int_T.new() int.from = 2 int.to = nil calculator.string(int).should == 'Integer[2, default]' end it 'should yield \'Float\' for PFloatType' do calculator.string(Puppet::Pops::Types::PFloatType.new()).should == 'Float' end it 'should yield \'Regexp\' for PRegexpType' do calculator.string(Puppet::Pops::Types::PRegexpType.new()).should == 'Regexp' end it 'should yield \'Regexp[/pat/]\' for parameterized PRegexpType' do t = Puppet::Pops::Types::PRegexpType.new() t.pattern = ('a/b') calculator.string(Puppet::Pops::Types::PRegexpType.new()).should == 'Regexp' end it 'should yield \'String\' for PStringType' do calculator.string(Puppet::Pops::Types::PStringType.new()).should == 'String' end it 'should yield \'String\' for PStringType with multiple values' do calculator.string(string_t('a', 'b', 'c')).should == 'String' end it 'should yield \'String\' and from/to for PStringType' do string_T = Puppet::Pops::Types::PStringType calculator.string(factory.constrain_size(string_T.new(), 1,1)).should == 'String[1, 1]' calculator.string(factory.constrain_size(string_T.new(), 1,2)).should == 'String[1, 2]' calculator.string(factory.constrain_size(string_T.new(), :default, 2)).should == 'String[default, 2]' calculator.string(factory.constrain_size(string_T.new(), 2, :default)).should == 'String[2, default]' end it 'should yield \'Array[Integer]\' for PArrayType[PIntegerType]' do t = Puppet::Pops::Types::PArrayType.new() t.element_type = Puppet::Pops::Types::PIntegerType.new() calculator.string(t).should == 'Array[Integer]' end it 'should yield \'Collection\' and from/to for PCollectionType' do col = collection_t() calculator.string(factory.constrain_size(col.copy, 1,1)).should == 'Collection[1, 1]' calculator.string(factory.constrain_size(col.copy, 1,2)).should == 'Collection[1, 2]' calculator.string(factory.constrain_size(col.copy, :default, 2)).should == 'Collection[default, 2]' calculator.string(factory.constrain_size(col.copy, 2, :default)).should == 'Collection[2, default]' end it 'should yield \'Array\' and from/to for PArrayType' do arr = array_t(string_t) calculator.string(factory.constrain_size(arr.copy, 1,1)).should == 'Array[String, 1, 1]' calculator.string(factory.constrain_size(arr.copy, 1,2)).should == 'Array[String, 1, 2]' calculator.string(factory.constrain_size(arr.copy, :default, 2)).should == 'Array[String, default, 2]' calculator.string(factory.constrain_size(arr.copy, 2, :default)).should == 'Array[String, 2, default]' end it 'should yield \'Tuple[Integer]\' for PTupleType[PIntegerType]' do t = Puppet::Pops::Types::PTupleType.new() t.addTypes(Puppet::Pops::Types::PIntegerType.new()) calculator.string(t).should == 'Tuple[Integer]' end it 'should yield \'Tuple[T, T,..]\' for PTupleType[T, T, ...]' do t = Puppet::Pops::Types::PTupleType.new() t.addTypes(Puppet::Pops::Types::PIntegerType.new()) t.addTypes(Puppet::Pops::Types::PIntegerType.new()) t.addTypes(Puppet::Pops::Types::PStringType.new()) calculator.string(t).should == 'Tuple[Integer, Integer, String]' end it 'should yield \'Tuple\' and from/to for PTupleType' do tuple_t = tuple_t(string_t) calculator.string(factory.constrain_size(tuple_t.copy, 1,1)).should == 'Tuple[String, 1, 1]' calculator.string(factory.constrain_size(tuple_t.copy, 1,2)).should == 'Tuple[String, 1, 2]' calculator.string(factory.constrain_size(tuple_t.copy, :default, 2)).should == 'Tuple[String, default, 2]' calculator.string(factory.constrain_size(tuple_t.copy, 2, :default)).should == 'Tuple[String, 2, default]' end it 'should yield \'Struct\' and details for PStructType' do struct_t = struct_t({'a'=>Integer, 'b'=>String}) calculator.string(struct_t).should == "Struct[{'a'=>Integer, 'b'=>String}]" struct_t = struct_t({}) calculator.string(struct_t).should == "Struct" end it 'should yield \'Hash[String, Integer]\' for PHashType[PStringType, PIntegerType]' do t = Puppet::Pops::Types::PHashType.new() t.key_type = Puppet::Pops::Types::PStringType.new() t.element_type = Puppet::Pops::Types::PIntegerType.new() calculator.string(t).should == 'Hash[String, Integer]' end it 'should yield \'Hash\' and from/to for PHashType' do hsh = hash_t(string_t, string_t) calculator.string(factory.constrain_size(hsh.copy, 1,1)).should == 'Hash[String, String, 1, 1]' calculator.string(factory.constrain_size(hsh.copy, 1,2)).should == 'Hash[String, String, 1, 2]' calculator.string(factory.constrain_size(hsh.copy, :default, 2)).should == 'Hash[String, String, default, 2]' calculator.string(factory.constrain_size(hsh.copy, 2, :default)).should == 'Hash[String, String, 2, default]' end it "should yield 'Class' for a PHostClassType" do t = Puppet::Pops::Types::PHostClassType.new() calculator.string(t).should == 'Class' end it "should yield 'Class[x]' for a PHostClassType[x]" do t = Puppet::Pops::Types::PHostClassType.new() t.class_name = 'x' calculator.string(t).should == 'Class[x]' end it "should yield 'Resource' for a PResourceType" do t = Puppet::Pops::Types::PResourceType.new() calculator.string(t).should == 'Resource' end it 'should yield \'File\' for a PResourceType[\'File\']' do t = Puppet::Pops::Types::PResourceType.new() t.type_name = 'File' calculator.string(t).should == 'File' end it "should yield 'File['/tmp/foo']' for a PResourceType['File', '/tmp/foo']" do t = Puppet::Pops::Types::PResourceType.new() t.type_name = 'File' t.title = '/tmp/foo' calculator.string(t).should == "File['/tmp/foo']" end it "should yield 'Enum[s,...]' for a PEnumType[s,...]" do t = enum_t('a', 'b', 'c') calculator.string(t).should == "Enum['a', 'b', 'c']" end it "should yield 'Pattern[/pat/,...]' for a PPatternType['pat',...]" do t = pattern_t('a') t2 = pattern_t('a', 'b', 'c') calculator.string(t).should == "Pattern[/a/]" calculator.string(t2).should == "Pattern[/a/, /b/, /c/]" end it "should escape special characters in the string for a PPatternType['pat',...]" do t = pattern_t('a/b') calculator.string(t).should == "Pattern[/a\\/b/]" end it "should yield 'Variant[t1,t2,...]' for a PVariantType[t1, t2,...]" do t1 = string_t() t2 = integer_t() t3 = pattern_t('a') t = variant_t(t1, t2, t3) calculator.string(t).should == "Variant[String, Integer, Pattern[/a/]]" end it "should yield 'Callable' for generic callable" do expect(calculator.string(all_callables_t)).to eql("Callable") end it "should yield 'Callable[0,0]' for callable without params" do expect(calculator.string(callable_t)).to eql("Callable[0, 0]") end it "should yield 'Callable[t,t]' for callable with typed parameters" do expect(calculator.string(callable_t(String, Integer))).to eql("Callable[String, Integer]") end it "should yield 'Callable[t,min,max]' for callable with size constraint (infinite max)" do expect(calculator.string(callable_t(String, 0))).to eql("Callable[String, 0, default]") end it "should yield 'Callable[t,min,max]' for callable with size constraint (capped max)" do expect(calculator.string(callable_t(String, 0, 3))).to eql("Callable[String, 0, 3]") end it "should yield 'Callable[min,max]' callable with size > 0" do expect(calculator.string(callable_t(0, 0))).to eql("Callable[0, 0]") expect(calculator.string(callable_t(0, 1))).to eql("Callable[0, 1]") expect(calculator.string(callable_t(0, :default))).to eql("Callable[0, default]") end it "should yield 'Callable[Callable]' for callable with block" do expect(calculator.string(callable_t(all_callables_t))).to eql("Callable[0, 0, Callable]") expect(calculator.string(callable_t(string_t, all_callables_t))).to eql("Callable[String, Callable]") expect(calculator.string(callable_t(string_t, 1,1, all_callables_t))).to eql("Callable[String, 1, 1, Callable]") end it "should yield Unit for a Unit type" do expect(calculator.string(unit_t)).to eql('Unit') end end context 'when processing meta type' do it 'should infer PType as the type of all other types' do ptype = Puppet::Pops::Types::PType calculator.infer(Puppet::Pops::Types::PNilType.new() ).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::PDataType.new() ).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::PScalarType.new() ).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::PStringType.new() ).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::PNumericType.new() ).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::PIntegerType.new() ).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::PFloatType.new() ).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::PRegexpType.new() ).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::PBooleanType.new() ).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::PCollectionType.new()).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::PArrayType.new() ).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::PHashType.new() ).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::PRuntimeType.new() ).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::PHostClassType.new() ).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::PResourceType.new() ).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::PEnumType.new() ).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::PPatternType.new() ).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::PVariantType.new() ).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::PTupleType.new() ).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::POptionalType.new() ).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::PCallableType.new() ).is_a?(ptype).should() == true end it 'should infer PType as the type of all other types' do ptype = Puppet::Pops::Types::PType calculator.string(calculator.infer(Puppet::Pops::Types::PNilType.new() )).should == "Type[Undef]" calculator.string(calculator.infer(Puppet::Pops::Types::PDataType.new() )).should == "Type[Data]" calculator.string(calculator.infer(Puppet::Pops::Types::PScalarType.new() )).should == "Type[Scalar]" calculator.string(calculator.infer(Puppet::Pops::Types::PStringType.new() )).should == "Type[String]" calculator.string(calculator.infer(Puppet::Pops::Types::PNumericType.new() )).should == "Type[Numeric]" calculator.string(calculator.infer(Puppet::Pops::Types::PIntegerType.new() )).should == "Type[Integer]" calculator.string(calculator.infer(Puppet::Pops::Types::PFloatType.new() )).should == "Type[Float]" calculator.string(calculator.infer(Puppet::Pops::Types::PRegexpType.new() )).should == "Type[Regexp]" calculator.string(calculator.infer(Puppet::Pops::Types::PBooleanType.new() )).should == "Type[Boolean]" calculator.string(calculator.infer(Puppet::Pops::Types::PCollectionType.new())).should == "Type[Collection]" calculator.string(calculator.infer(Puppet::Pops::Types::PArrayType.new() )).should == "Type[Array[?]]" calculator.string(calculator.infer(Puppet::Pops::Types::PHashType.new() )).should == "Type[Hash[?, ?]]" calculator.string(calculator.infer(Puppet::Pops::Types::PRuntimeType.new() )).should == "Type[Runtime[?, ?]]" calculator.string(calculator.infer(Puppet::Pops::Types::PHostClassType.new() )).should == "Type[Class]" calculator.string(calculator.infer(Puppet::Pops::Types::PResourceType.new() )).should == "Type[Resource]" calculator.string(calculator.infer(Puppet::Pops::Types::PEnumType.new() )).should == "Type[Enum]" calculator.string(calculator.infer(Puppet::Pops::Types::PVariantType.new() )).should == "Type[Variant]" calculator.string(calculator.infer(Puppet::Pops::Types::PPatternType.new() )).should == "Type[Pattern]" calculator.string(calculator.infer(Puppet::Pops::Types::PTupleType.new() )).should == "Type[Tuple]" calculator.string(calculator.infer(Puppet::Pops::Types::POptionalType.new() )).should == "Type[Optional]" calculator.string(calculator.infer(Puppet::Pops::Types::PCallableType.new() )).should == "Type[Callable]" calculator.infer(Puppet::Pops::Types::PResourceType.new(:type_name => 'foo::fee::fum')).to_s.should == "Type[Foo::Fee::Fum]" calculator.string(calculator.infer(Puppet::Pops::Types::PResourceType.new(:type_name => 'foo::fee::fum'))).should == "Type[Foo::Fee::Fum]" calculator.infer(Puppet::Pops::Types::PResourceType.new(:type_name => 'Foo::Fee::Fum')).to_s.should == "Type[Foo::Fee::Fum]" end it "computes the common type of PType's type parameter" do int_t = Puppet::Pops::Types::PIntegerType.new() string_t = Puppet::Pops::Types::PStringType.new() calculator.string(calculator.infer([int_t])).should == "Array[Type[Integer], 1, 1]" calculator.string(calculator.infer([int_t, string_t])).should == "Array[Type[Scalar], 2, 2]" end it 'should infer PType as the type of ruby classes' do class Foo end [Object, Numeric, Integer, Fixnum, Bignum, Float, String, Regexp, Array, Hash, Foo].each do |c| calculator.infer(c).is_a?(Puppet::Pops::Types::PType).should() == true end end it 'should infer PType as the type of PType (meta regression short-circuit)' do calculator.infer(Puppet::Pops::Types::PType.new()).is_a?(Puppet::Pops::Types::PType).should() == true end it 'computes instance? to be true if parameterized and type match' do int_t = Puppet::Pops::Types::PIntegerType.new() type_t = Puppet::Pops::Types::TypeFactory.type_type(int_t) type_type_t = Puppet::Pops::Types::TypeFactory.type_type(type_t) calculator.instance?(type_type_t, type_t).should == true end it 'computes instance? to be false if parameterized and type do not match' do int_t = Puppet::Pops::Types::PIntegerType.new() string_t = Puppet::Pops::Types::PStringType.new() type_t = Puppet::Pops::Types::TypeFactory.type_type(int_t) type_t2 = Puppet::Pops::Types::TypeFactory.type_type(string_t) type_type_t = Puppet::Pops::Types::TypeFactory.type_type(type_t) # i.e. Type[Integer] =~ Type[Type[Integer]] # false calculator.instance?(type_type_t, type_t2).should == false end it 'computes instance? to be true if unparameterized and matched against a type[?]' do int_t = Puppet::Pops::Types::PIntegerType.new() type_t = Puppet::Pops::Types::TypeFactory.type_type(int_t) calculator.instance?(Puppet::Pops::Types::PType.new, type_t).should == true end end context "when asking for an enumerable " do it "should produce an enumerable for an Integer range that is not infinite" do t = Puppet::Pops::Types::PIntegerType.new() t.from = 1 t.to = 10 calculator.enumerable(t).respond_to?(:each).should == true end it "should not produce an enumerable for an Integer range that has an infinite side" do t = Puppet::Pops::Types::PIntegerType.new() t.from = nil t.to = 10 calculator.enumerable(t).should == nil t = Puppet::Pops::Types::PIntegerType.new() t.from = 1 t.to = nil calculator.enumerable(t).should == nil end it "all but Integer range are not enumerable" do [Object, Numeric, Float, String, Regexp, Array, Hash].each do |t| calculator.enumerable(calculator.type(t)).should == nil end end end context "when dealing with different types of inference" do it "an instance specific inference is produced by infer" do calculator.infer(['a','b']).element_type.values.should == ['a', 'b'] end it "a generic inference is produced using infer_generic" do calculator.infer_generic(['a','b']).element_type.values.should == [] end it "a generic result is created by generalize! given an instance specific result for an Array" do generic = calculator.infer(['a','b']) generic.element_type.values.should == ['a', 'b'] calculator.generalize!(generic) generic.element_type.values.should == [] end it "a generic result is created by generalize! given an instance specific result for a Hash" do generic = calculator.infer({'a' =>1,'b' => 2}) generic.key_type.values.sort.should == ['a', 'b'] generic.element_type.from.should == 1 generic.element_type.to.should == 2 calculator.generalize!(generic) generic.key_type.values.should == [] generic.element_type.from.should == nil generic.element_type.to.should == nil end it "does not reduce by combining types when using infer_set" do element_type = calculator.infer(['a','b',1,2]).element_type element_type.class.should == Puppet::Pops::Types::PScalarType inferred_type = calculator.infer_set(['a','b',1,2]) inferred_type.class.should == Puppet::Pops::Types::PTupleType element_types = inferred_type.types element_types[0].class.should == Puppet::Pops::Types::PStringType element_types[1].class.should == Puppet::Pops::Types::PStringType element_types[2].class.should == Puppet::Pops::Types::PIntegerType element_types[3].class.should == Puppet::Pops::Types::PIntegerType end it "does not reduce by combining types when using infer_set and values are undef" do element_type = calculator.infer(['a',nil]).element_type element_type.class.should == Puppet::Pops::Types::PStringType inferred_type = calculator.infer_set(['a',nil]) inferred_type.class.should == Puppet::Pops::Types::PTupleType element_types = inferred_type.types element_types[0].class.should == Puppet::Pops::Types::PStringType element_types[1].class.should == Puppet::Pops::Types::PNilType end end context 'when determening callability' do context 'and given is exact' do it 'with callable' do required = callable_t(string_t) given = callable_t(string_t) calculator.callable?(required, given).should == true end it 'with args tuple' do required = callable_t(string_t) given = tuple_t(string_t) calculator.callable?(required, given).should == true end it 'with args tuple having a block' do required = callable_t(string_t, callable_t(string_t)) given = tuple_t(string_t, callable_t(string_t)) calculator.callable?(required, given).should == true end it 'with args array' do required = callable_t(string_t) given = array_t(string_t) factory.constrain_size(given, 1, 1) calculator.callable?(required, given).should == true end end context 'and given is more generic' do it 'with callable' do required = callable_t(string_t) given = callable_t(object_t) calculator.callable?(required, given).should == true end it 'with args tuple' do required = callable_t(string_t) given = tuple_t(object_t) calculator.callable?(required, given).should == false end it 'with args tuple having a block' do required = callable_t(string_t, callable_t(string_t)) given = tuple_t(string_t, callable_t(object_t)) calculator.callable?(required, given).should == true end it 'with args tuple having a block with captures rest' do required = callable_t(string_t, callable_t(string_t)) given = tuple_t(string_t, callable_t(object_t, 0, :default)) calculator.callable?(required, given).should == true end end context 'and given is more specific' do it 'with callable' do required = callable_t(object_t) given = callable_t(string_t) calculator.callable?(required, given).should == false end it 'with args tuple' do required = callable_t(object_t) given = tuple_t(string_t) calculator.callable?(required, given).should == true end it 'with args tuple having a block' do required = callable_t(string_t, callable_t(object_t)) given = tuple_t(string_t, callable_t(string_t)) calculator.callable?(required, given).should == false end it 'with args tuple having a block with captures rest' do required = callable_t(string_t, callable_t(object_t)) given = tuple_t(string_t, callable_t(string_t, 0, :default)) calculator.callable?(required, given).should == false end end end matcher :be_assignable_to do |type| calc = Puppet::Pops::Types::TypeCalculator.new match do |actual| calc.assignable?(type, actual) end failure_message_for_should do |actual| "#{calc.string(actual)} should be assignable to #{calc.string(type)}" end failure_message_for_should_not do |actual| "#{calc.string(actual)} is assignable to #{calc.string(type)} when it should not" end end end diff --git a/spec/unit/property_spec.rb b/spec/unit/property_spec.rb index df855ac5b..78ae50e35 100755 --- a/spec/unit/property_spec.rb +++ b/spec/unit/property_spec.rb @@ -1,511 +1,511 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/property' describe Puppet::Property do let :resource do Puppet::Type.type(:host).new :name => "foo" end let :subclass do # We need a completely fresh subclass every time, because we modify both # class and instance level things inside the tests. subclass = Class.new(Puppet::Property) do class << self attr_accessor :name end @name = :foo end subclass.initvars subclass end let :property do subclass.new :resource => resource end it "should be able to look up the modified name for a given value" do subclass.newvalue(:foo) subclass.value_name("foo").should == :foo end it "should be able to look up the modified name for a given value matching a regex" do subclass.newvalue(%r{.}) subclass.value_name("foo").should == %r{.} end it "should be able to look up a given value option" do subclass.newvalue(:foo, :event => :whatever) subclass.value_option(:foo, :event).should == :whatever end it "should be able to specify required features" do subclass.should respond_to(:required_features=) end {"one" => [:one],:one => [:one],%w{a} => [:a],[:b] => [:b],%w{one two} => [:one,:two],[:a,:b] => [:a,:b]}.each { |in_value,out_value| it "should always convert required features into an array of symbols (e.g. #{in_value.inspect} --> #{out_value.inspect})" do subclass.required_features = in_value subclass.required_features.should == out_value end } it "should return its name as a string when converted to a string" do property.to_s.should == property.name.to_s end describe "when returning the default event name" do it "should use the current 'should' value to pick the event name" do property.expects(:should).returns "myvalue" subclass.expects(:value_option).with('myvalue', :event).returns :event_name property.event_name end it "should return any event defined with the specified value" do property.expects(:should).returns :myval subclass.expects(:value_option).with(:myval, :event).returns :event_name property.event_name.should == :event_name end describe "and the property is 'ensure'" do before :each do property.stubs(:name).returns :ensure resource.expects(:type).returns :mytype end it "should use _created if the 'should' value is 'present'" do property.expects(:should).returns :present property.event_name.should == :mytype_created end it "should use _removed if the 'should' value is 'absent'" do property.expects(:should).returns :absent property.event_name.should == :mytype_removed end it "should use _changed if the 'should' value is not 'absent' or 'present'" do property.expects(:should).returns :foo property.event_name.should == :mytype_changed end it "should use _changed if the 'should value is nil" do property.expects(:should).returns nil property.event_name.should == :mytype_changed end end it "should use _changed if the property is not 'ensure'" do property.stubs(:name).returns :myparam property.expects(:should).returns :foo property.event_name.should == :myparam_changed end it "should use _changed if no 'should' value is set" do property.stubs(:name).returns :myparam property.expects(:should).returns nil property.event_name.should == :myparam_changed end end describe "when creating an event" do before :each do property.stubs(:should).returns "myval" end it "should use an event from the resource as the base event" do event = Puppet::Transaction::Event.new resource.expects(:event).returns event property.event.should equal(event) end it "should have the default event name" do property.expects(:event_name).returns :my_event property.event.name.should == :my_event end it "should have the property's name" do property.event.property.should == property.name.to_s end it "should have the 'should' value set" do property.stubs(:should).returns "foo" property.event.desired_value.should == "foo" end it "should provide its path as the source description" do property.stubs(:path).returns "/my/param" property.event.source_description.should == "/my/param" end it "should have the 'invalidate_refreshes' value set if set on a value" do property.stubs(:event_name).returns :my_event property.stubs(:should).returns "foo" foo = mock() foo.expects(:invalidate_refreshes).returns(true) collection = mock() collection.expects(:match?).with("foo").returns(foo) property.class.stubs(:value_collection).returns(collection) - property.event.invalidate_refreshes.should be_true + property.event.invalidate_refreshes.should be_truthy end end describe "when defining new values" do it "should define a method for each value created with a block that's not a regex" do subclass.newvalue(:foo) { } property.must respond_to(:set_foo) end end describe "when assigning the value" do it "should just set the 'should' value" do property.value = "foo" property.should.must == "foo" end it "should validate each value separately" do property.expects(:validate).with("one") property.expects(:validate).with("two") property.value = %w{one two} end it "should munge each value separately and use any result as the actual value" do property.expects(:munge).with("one").returns :one property.expects(:munge).with("two").returns :two # Do this so we get the whole array back. subclass.array_matching = :all property.value = %w{one two} property.should.must == [:one, :two] end it "should return any set value" do (property.value = :one).should == :one end end describe "when returning the value" do it "should return nil if no value is set" do property.should.must be_nil end it "should return the first set 'should' value if :array_matching is set to :first" do subclass.array_matching = :first property.should = %w{one two} property.should.must == "one" end it "should return all set 'should' values as an array if :array_matching is set to :all" do subclass.array_matching = :all property.should = %w{one two} property.should.must == %w{one two} end it "should default to :first array_matching" do subclass.array_matching.should == :first end it "should unmunge the returned value if :array_matching is set to :first" do property.class.unmunge do |v| v.to_sym end subclass.array_matching = :first property.should = %w{one two} property.should.must == :one end it "should unmunge all the returned values if :array_matching is set to :all" do property.class.unmunge do |v| v.to_sym end subclass.array_matching = :all property.should = %w{one two} property.should.must == [:one, :two] end end describe "when validating values" do it "should do nothing if no values or regexes have been defined" do lambda { property.should = "foo" }.should_not raise_error end it "should fail if the value is not a defined value or alias and does not match a regex" do subclass.newvalue(:foo) lambda { property.should = "bar" }.should raise_error end it "should succeeed if the value is one of the defined values" do subclass.newvalue(:foo) lambda { property.should = :foo }.should_not raise_error end it "should succeeed if the value is one of the defined values even if the definition uses a symbol and the validation uses a string" do subclass.newvalue(:foo) lambda { property.should = "foo" }.should_not raise_error end it "should succeeed if the value is one of the defined values even if the definition uses a string and the validation uses a symbol" do subclass.newvalue("foo") lambda { property.should = :foo }.should_not raise_error end it "should succeed if the value is one of the defined aliases" do subclass.newvalue("foo") subclass.aliasvalue("bar", "foo") lambda { property.should = :bar }.should_not raise_error end it "should succeed if the value matches one of the regexes" do subclass.newvalue(/./) lambda { property.should = "bar" }.should_not raise_error end it "should validate that all required features are present" do subclass.newvalue(:foo, :required_features => [:a, :b]) resource.provider.expects(:satisfies?).with([:a, :b]).returns true property.should = :foo end it "should fail if required features are missing" do subclass.newvalue(:foo, :required_features => [:a, :b]) resource.provider.expects(:satisfies?).with([:a, :b]).returns false lambda { property.should = :foo }.should raise_error(Puppet::Error) end it "should internally raise an ArgumentError if required features are missing" do subclass.newvalue(:foo, :required_features => [:a, :b]) resource.provider.expects(:satisfies?).with([:a, :b]).returns false lambda { property.validate_features_per_value :foo }.should raise_error(ArgumentError) end it "should validate that all required features are present for regexes" do value = subclass.newvalue(/./, :required_features => [:a, :b]) resource.provider.expects(:satisfies?).with([:a, :b]).returns true property.should = "foo" end it "should support specifying an individual required feature" do value = subclass.newvalue(/./, :required_features => :a) resource.provider.expects(:satisfies?).returns true property.should = "foo" end end describe "when munging values" do it "should do nothing if no values or regexes have been defined" do property.munge("foo").should == "foo" end it "should return return any matching defined values" do subclass.newvalue(:foo) property.munge("foo").should == :foo end it "should return any matching aliases" do subclass.newvalue(:foo) subclass.aliasvalue(:bar, :foo) property.munge("bar").should == :foo end it "should return the value if it matches a regex" do subclass.newvalue(/./) property.munge("bar").should == "bar" end it "should return the value if no other option is matched" do subclass.newvalue(:foo) property.munge("bar").should == "bar" end end describe "when syncing the 'should' value" do it "should set the value" do subclass.newvalue(:foo) property.should = :foo property.expects(:set).with(:foo) property.sync end end describe "when setting a value" do it "should catch exceptions and raise Puppet::Error" do subclass.newvalue(:foo) { raise "eh" } lambda { property.set(:foo) }.should raise_error(Puppet::Error) end it "fails when the provider does not handle the attribute" do subclass.name = "unknown" lambda { property.set(:a_value) }.should raise_error(Puppet::Error) end it "propogates the errors about missing methods from the provider" do provider = resource.provider def provider.bad_method=(value) value.this_method_does_not_exist end subclass.name = :bad_method lambda { property.set(:a_value) }.should raise_error(NoMethodError, /this_method_does_not_exist/) end describe "that was defined without a block" do it "should call the settor on the provider" do subclass.newvalue(:bar) resource.provider.expects(:foo=).with :bar property.set(:bar) end end describe "that was defined with a block" do it "should call the method created for the value if the value is not a regex" do subclass.newvalue(:bar) {} property.expects(:set_bar) property.set(:bar) end it "should call the provided block if the value is a regex" do subclass.newvalue(/./) { self.test } property.expects(:test) property.set("foo") end end end describe "when producing a change log" do it "should say 'defined' when the current value is 'absent'" do property.change_to_s(:absent, "foo").should =~ /^defined/ end it "should say 'undefined' when the new value is 'absent'" do property.change_to_s("foo", :absent).should =~ /^undefined/ end it "should say 'changed' when neither value is 'absent'" do property.change_to_s("foo", "bar").should =~ /changed/ end end shared_examples_for "#insync?" do # We share a lot of behaviour between the all and first matching, so we # use a shared behaviour set to emulate that. The outside world makes # sure the class, etc, point to the right content. [[], [12], [12, 13]].each do |input| it "should return true if should is empty with is => #{input.inspect}" do property.should = [] property.must be_insync(input) end end end describe "#insync?" do context "array_matching :all" do # `@should` is an array of scalar values, and `is` is an array of scalar values. before :each do property.class.array_matching = :all end it_should_behave_like "#insync?" context "if the should value is an array" do before :each do property.should = [1, 2] end it "should match if is exactly matches" do property.must be_insync [1, 2] end it "should match if it matches, but all stringified" do property.must be_insync ["1", "2"] end it "should not match if some-but-not-all values are stringified" do property.must_not be_insync ["1", 2] property.must_not be_insync [1, "2"] end it "should not match if order is different but content the same" do property.must_not be_insync [2, 1] end it "should not match if there are more items in should than is" do property.must_not be_insync [1] end it "should not match if there are less items in should than is" do property.must_not be_insync [1, 2, 3] end it "should not match if `is` is empty but `should` isn't" do property.must_not be_insync [] end end end context "array_matching :first" do # `@should` is an array of scalar values, and `is` is a scalar value. before :each do property.class.array_matching = :first end it_should_behave_like "#insync?" [[1], # only the value [1, 2], # matching value first [2, 1], # matching value last [0, 1, 2], # matching value in the middle ].each do |input| it "should by true if one unmodified should value of #{input.inspect} matches what is" do property.should = input property.must be_insync 1 end it "should be true if one stringified should value of #{input.inspect} matches what is" do property.should = input property.must be_insync "1" end end it "should not match if we expect a string but get the non-stringified value" do property.should = ["1"] property.must_not be_insync 1 end [[0], [0, 2]].each do |input| it "should not match if no should values match what is" do property.should = input property.must_not be_insync 1 property.must_not be_insync "1" # shouldn't match either. end end end end describe "#property_matches?" do [1, "1", [1], :one].each do |input| it "should treat two equal objects as equal (#{input.inspect})" do - property.property_matches?(input, input).should be_true + property.property_matches?(input, input).should be_truthy end end it "should treat two objects as equal if the first argument is the stringified version of the second" do - property.property_matches?("1", 1).should be_true + property.property_matches?("1", 1).should be_truthy end it "should NOT treat two objects as equal if the first argument is not a string, and the second argument is a string, even if it stringifies to the first" do - property.property_matches?(1, "1").should be_false + property.property_matches?(1, "1").should be_falsey end end end diff --git a/spec/unit/provider/cron/parsed_spec.rb b/spec/unit/provider/cron/parsed_spec.rb index 1a038eae5..53224e32a 100644 --- a/spec/unit/provider/cron/parsed_spec.rb +++ b/spec/unit/provider/cron/parsed_spec.rb @@ -1,357 +1,357 @@ #!/usr/bin/env rspec require 'spec_helper' describe Puppet::Type.type(:cron).provider(:crontab) do let :provider do described_class.new(:command => '/bin/true') end let :resource do Puppet::Type.type(:cron).new( :minute => %w{0 15 30 45}, :hour => %w{8-18 20-22}, :monthday => %w{31}, :month => %w{12}, :weekday => %w{7}, :name => 'basic', :command => '/bin/true', :target => 'root', :provider => provider ) end let :resource_special do Puppet::Type.type(:cron).new( :special => 'reboot', :name => 'special', :command => '/bin/true', :target => 'nobody' ) end let :resource_sparse do Puppet::Type.type(:cron).new( :minute => %w{42}, :target => 'root', :name => 'sparse' ) end let :record_special do { :record_type => :crontab, :special => 'reboot', :command => '/bin/true', :on_disk => true, :target => 'nobody' } end let :record do { :record_type => :crontab, :minute => %w{0 15 30 45}, :hour => %w{8-18 20-22}, :monthday => %w{31}, :month => %w{12}, :weekday => %w{7}, :special => :absent, :command => '/bin/true', :on_disk => true, :target => 'root' } end describe "when determining the correct filetype" do it "should use the suntab filetype on Solaris" do Facter.stubs(:value).with(:osfamily).returns 'Solaris' described_class.filetype.should == Puppet::Util::FileType::FileTypeSuntab end it "should use the aixtab filetype on AIX" do Facter.stubs(:value).with(:osfamily).returns 'AIX' described_class.filetype.should == Puppet::Util::FileType::FileTypeAixtab end it "should use the crontab filetype on other platforms" do Facter.stubs(:value).with(:osfamily).returns 'Not a real operating system family' described_class.filetype.should == Puppet::Util::FileType::FileTypeCrontab end end # I'd use ENV.expects(:[]).with('USER') but this does not work because # ENV["USER"] is evaluated at load time. describe "when determining the default target" do it "should use the current user #{ENV['USER']}", :if => ENV['USER'] do described_class.default_target.should == ENV['USER'] end it "should fallback to root", :unless => ENV['USER'] do described_class.default_target.should == "root" end end describe ".targets" do let(:tabs) { [ described_class.default_target ] + %w{foo bar} } before do File.expects(:readable?).returns true File.stubs(:file?).returns true File.stubs(:writable?).returns true end after do File.unstub :readable?, :file?, :writable? Dir.unstub :foreach end it "should add all crontabs as targets" do Dir.expects(:foreach).multiple_yields(*tabs) described_class.targets.should == tabs end end describe "when parsing a record" do it "should parse a comment" do described_class.parse_line("# This is a test").should == { :record_type => :comment, :line => "# This is a test", } end it "should get the resource name of a PUPPET NAME comment" do described_class.parse_line('# Puppet Name: My Fancy Cronjob').should == { :record_type => :comment, :name => 'My Fancy Cronjob', :line => '# Puppet Name: My Fancy Cronjob', } end it "should ignore blank lines" do described_class.parse_line('').should == {:record_type => :blank, :line => ''} described_class.parse_line(' ').should == {:record_type => :blank, :line => ' '} described_class.parse_line("\t").should == {:record_type => :blank, :line => "\t"} described_class.parse_line(" \t ").should == {:record_type => :blank, :line => " \t "} end it "should extract environment assignments" do # man 5 crontab: MAILTO="" with no value can be used to surpress sending # mails at all described_class.parse_line('MAILTO=""').should == {:record_type => :environment, :line => 'MAILTO=""'} described_class.parse_line('FOO=BAR').should == {:record_type => :environment, :line => 'FOO=BAR'} described_class.parse_line('FOO_BAR=BAR').should == {:record_type => :environment, :line => 'FOO_BAR=BAR'} end it "should extract a cron entry" do described_class.parse_line('* * * * * /bin/true').should == { :record_type => :crontab, :hour => :absent, :minute => :absent, :month => :absent, :weekday => :absent, :monthday => :absent, :special => :absent, :command => '/bin/true' } described_class.parse_line('0,15,30,45 8-18,20-22 31 12 7 /bin/true').should == { :record_type => :crontab, :minute => %w{0 15 30 45}, :hour => %w{8-18 20-22}, :monthday => %w{31}, :month => %w{12}, :weekday => %w{7}, :special => :absent, :command => '/bin/true' } # A percent sign will cause the rest of the string to be passed as # standard input and will also act as a newline character. Not sure # if puppet should convert % to a \n as the command property so the # test covers the current behaviour: Do not do any conversions described_class.parse_line('0 22 * * 1-5 mail -s "It\'s 10pm" joe%Joe,%%Where are your kids?%').should == { :record_type => :crontab, :minute => %w{0}, :hour => %w{22}, :monthday => :absent, :month => :absent, :weekday => %w{1-5}, :special => :absent, :command => 'mail -s "It\'s 10pm" joe%Joe,%%Where are your kids?%' } end describe "it should support special strings" do ['reboot','yearly','anually','monthly', 'weekly', 'daily', 'midnight', 'hourly'].each do |special| it "should support @#{special}" do described_class.parse_line("@#{special} /bin/true").should == { :record_type => :crontab, :hour => :absent, :minute => :absent, :month => :absent, :weekday => :absent, :monthday => :absent, :special => special, :command => '/bin/true' } end end end end describe ".instances" do before :each do described_class.stubs(:default_target).returns 'foobar' end describe "on linux" do before do Facter.stubs(:value).with(:osfamily).returns 'Linux' Facter.stubs(:value).with(:operatingsystem) end it "should contain no resources for a user who has no crontab" do # `crontab...` does only capture stdout here. On vixie-cron-4.1 # STDERR shows "no crontab for foobar" but stderr is ignored as # well as the exitcode. described_class.target_object('foobar').expects(:`).with('crontab -u foobar -l 2>/dev/null').returns "" described_class.instances.select { |resource| resource.get('target') == 'foobar' }.should be_empty end it "should contain no resources for a user who is absent" do # `crontab...` does only capture stdout. On vixie-cron-4.1 # STDERR shows "crontab: user `foobar' unknown" but stderr is # ignored as well as the exitcode described_class.target_object('foobar').expects(:`).with('crontab -u foobar -l 2>/dev/null').returns "" described_class.instances.select { |resource| resource.get('target') == 'foobar' }.should be_empty end it "should be able to create records from not-managed records" do described_class.stubs(:target_object).returns File.new(my_fixture('simple')) parameters = described_class.instances.map do |p| h = {:name => p.get(:name)} Puppet::Type.type(:cron).validproperties.each do |property| h[property] = p.get(property) end h end expect(parameters[0][:name]).to match(%r{unmanaged:\$HOME/bin/daily.job_>>_\$HOME/tmp/out_2>&1-\d+}) expect(parameters[0][:minute]).to eq(['5']) expect(parameters[0][:hour]).to eq(['0']) expect(parameters[0][:weekday]).to eq(:absent) expect(parameters[0][:month]).to eq(:absent) expect(parameters[0][:monthday]).to eq(:absent) expect(parameters[0][:special]).to eq(:absent) expect(parameters[0][:command]).to match(%r{\$HOME/bin/daily.job >> \$HOME/tmp/out 2>&1}) expect(parameters[0][:ensure]).to eq(:present) expect(parameters[0][:environment]).to eq(:absent) expect(parameters[0][:user]).to eq(:absent) expect(parameters[1][:name]).to match(%r{unmanaged:\$HOME/bin/monthly-\d+}) expect(parameters[1][:minute]).to eq(['15']) expect(parameters[1][:hour]).to eq(['14']) expect(parameters[1][:weekday]).to eq(:absent) expect(parameters[1][:month]).to eq(:absent) expect(parameters[1][:monthday]).to eq(['1']) expect(parameters[1][:special]).to eq(:absent) expect(parameters[1][:command]).to match(%r{\$HOME/bin/monthly}) expect(parameters[1][:ensure]).to eq(:present) expect(parameters[1][:environment]).to eq(:absent) expect(parameters[1][:user]).to eq(:absent) expect(parameters[1][:target]).to eq('foobar') end it "should be able to parse puppet managed cronjobs" do described_class.stubs(:target_object).returns File.new(my_fixture('managed')) described_class.instances.map do |p| h = {:name => p.get(:name)} Puppet::Type.type(:cron).validproperties.each do |property| h[property] = p.get(property) end h end.should == [ { :name => 'real_job', :minute => :absent, :hour => :absent, :weekday => :absent, :month => :absent, :monthday => :absent, :special => :absent, :command => '/bin/true', :ensure => :present, :environment => :absent, :user => :absent, :target => 'foobar' }, { :name => 'complex_job', :minute => :absent, :hour => :absent, :weekday => :absent, :month => :absent, :monthday => :absent, :special => 'reboot', :command => '/bin/true >> /dev/null 2>&1', :ensure => :present, :environment => [ 'MAILTO=foo@example.com', 'SHELL=/bin/sh' ], :user => :absent, :target => 'foobar' } ] end end end describe ".match" do describe "normal records" do it "should match when all fields are the same" do described_class.match(record,{resource[:name] => resource}).must == resource end { :minute => %w{0 15 31 45}, :hour => %w{8-18}, :monthday => %w{30 31}, :month => %w{12 23}, :weekday => %w{4}, :command => '/bin/false', :target => 'nobody' }.each_pair do |field, new_value| it "should not match a record when #{field} does not match" do record[field] = new_value - described_class.match(record,{resource[:name] => resource}).must be_false + described_class.match(record,{resource[:name] => resource}).must be_falsey end end end describe "special records" do it "should match when all fields are the same" do described_class.match(record_special,{resource_special[:name] => resource_special}).must == resource_special end { :special => 'monthly', :command => '/bin/false', :target => 'root' }.each_pair do |field, new_value| it "should not match a record when #{field} does not match" do record_special[field] = new_value - described_class.match(record_special,{resource_special[:name] => resource_special}).must be_false + described_class.match(record_special,{resource_special[:name] => resource_special}).must be_falsey end end end describe "with a resource without a command" do it "should not raise an error" do expect { described_class.match(record,{resource_sparse[:name] => resource_sparse}) }.to_not raise_error end end end end diff --git a/spec/unit/provider/group/windows_adsi_spec.rb b/spec/unit/provider/group/windows_adsi_spec.rb index 22c482c32..fff64b081 100644 --- a/spec/unit/provider/group/windows_adsi_spec.rb +++ b/spec/unit/provider/group/windows_adsi_spec.rb @@ -1,223 +1,223 @@ #!/usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:group).provider(:windows_adsi), :if => Puppet.features.microsoft_windows? do let(:resource) do Puppet::Type.type(:group).new( :title => 'testers', :provider => :windows_adsi ) end let(:provider) { resource.provider } let(:connection) { stub 'connection' } before :each do Puppet::Util::Windows::ADSI.stubs(:computer_name).returns('testcomputername') Puppet::Util::Windows::ADSI.stubs(:connect).returns connection end describe ".instances" do it "should enumerate all groups" do names = ['group1', 'group2', 'group3'] stub_groups = names.map{|n| stub(:name => n)} connection.stubs(:execquery).with('select name from win32_group where localaccount = "TRUE"').returns stub_groups described_class.instances.map(&:name).should =~ names end end describe "group type :members property helpers" do let(:user1) { stub(:account => 'user1', :domain => '.', :to_s => 'user1sid') } let(:user2) { stub(:account => 'user2', :domain => '.', :to_s => 'user2sid') } let(:user3) { stub(:account => 'user3', :domain => '.', :to_s => 'user3sid') } before :each do Puppet::Util::Windows::SID.stubs(:name_to_sid_object).with('user1').returns(user1) Puppet::Util::Windows::SID.stubs(:name_to_sid_object).with('user2').returns(user2) Puppet::Util::Windows::SID.stubs(:name_to_sid_object).with('user3').returns(user3) end describe "#members_insync?" do it "should return false when current is nil" do - provider.members_insync?(nil, ['user2']).should be_false + provider.members_insync?(nil, ['user2']).should be_falsey end it "should return false when should is nil" do - provider.members_insync?(['user1'], nil).should be_false + provider.members_insync?(['user1'], nil).should be_falsey end it "should return true for same lists of members" do - provider.members_insync?(['user1', 'user2'], ['user1', 'user2']).should be_true + provider.members_insync?(['user1', 'user2'], ['user1', 'user2']).should be_truthy end it "should return true for same lists of unordered members" do - provider.members_insync?(['user1', 'user2'], ['user2', 'user1']).should be_true + provider.members_insync?(['user1', 'user2'], ['user2', 'user1']).should be_truthy end it "should return true for same lists of members irrespective of duplicates" do - provider.members_insync?(['user1', 'user2', 'user2'], ['user2', 'user1', 'user1']).should be_true + provider.members_insync?(['user1', 'user2', 'user2'], ['user2', 'user1', 'user1']).should be_truthy end context "when auth_membership => true" do before :each do resource[:auth_membership] = true end it "should return false when current contains different users than should" do - provider.members_insync?(['user1'], ['user2']).should be_false + provider.members_insync?(['user1'], ['user2']).should be_falsey end it "should return false when current contains members and should is empty" do - provider.members_insync?(['user1'], []).should be_false + provider.members_insync?(['user1'], []).should be_falsey end it "should return false when current is empty and should contains members" do - provider.members_insync?([], ['user2']).should be_false + provider.members_insync?([], ['user2']).should be_falsey end it "should return false when should user(s) are not the only items in the current" do - provider.members_insync?(['user1', 'user2'], ['user1']).should be_false + provider.members_insync?(['user1', 'user2'], ['user1']).should be_falsey end end context "when auth_membership => false" do before :each do # this is also the default resource[:auth_membership] = false end it "should return false when current contains different users than should" do - provider.members_insync?(['user1'], ['user2']).should be_false + provider.members_insync?(['user1'], ['user2']).should be_falsey end it "should return true when current contains members and should is empty" do - provider.members_insync?(['user1'], []).should be_true + provider.members_insync?(['user1'], []).should be_truthy end it "should return false when current is empty and should contains members" do - provider.members_insync?([], ['user2']).should be_false + provider.members_insync?([], ['user2']).should be_falsey end it "should return true when current user(s) contains at least the should list" do - provider.members_insync?(['user1','user2'], ['user1']).should be_true + provider.members_insync?(['user1','user2'], ['user1']).should be_truthy end it "should return true when current user(s) contains at least the should list, even unordered" do - provider.members_insync?(['user3','user1','user2'], ['user2','user1']).should be_true + provider.members_insync?(['user3','user1','user2'], ['user2','user1']).should be_truthy end end end describe "#members_to_s" do it "should return an empty string on non-array input" do [Object.new, {}, 1, :symbol, ''].each do |input| provider.members_to_s(input).should be_empty end end it "should return an empty string on empty or nil users" do provider.members_to_s([]).should be_empty provider.members_to_s(nil).should be_empty end it "should return a user string like DOMAIN\\USER" do provider.members_to_s(['user1']).should == '.\user1' end it "should return a user string like DOMAIN\\USER,DOMAIN2\\USER2" do provider.members_to_s(['user1', 'user2']).should == '.\user1,.\user2' end end end describe "when managing members" do before :each do resource[:auth_membership] = true end it "should be able to provide a list of members" do provider.group.stubs(:members).returns ['user1', 'user2', 'user3'] provider.members.should =~ ['user1', 'user2', 'user3'] end it "should be able to set group members" do provider.group.stubs(:members).returns ['user1', 'user2'] member_sids = [ stub(:account => 'user1', :domain => 'testcomputername'), stub(:account => 'user2', :domain => 'testcomputername'), stub(:account => 'user3', :domain => 'testcomputername'), ] provider.group.stubs(:member_sids).returns(member_sids[0..1]) Puppet::Util::Windows::SID.expects(:name_to_sid_object).with('user2').returns(member_sids[1]) Puppet::Util::Windows::SID.expects(:name_to_sid_object).with('user3').returns(member_sids[2]) provider.group.expects(:remove_member_sids).with(member_sids[0]) provider.group.expects(:add_member_sids).with(member_sids[2]) provider.members = ['user2', 'user3'] end end describe 'when creating groups' do it "should be able to create a group" do resource[:members] = ['user1', 'user2'] group = stub 'group' Puppet::Util::Windows::ADSI::Group.expects(:create).with('testers').returns group create = sequence('create') group.expects(:commit).in_sequence(create) # due to PUP-1967, defaultto false will set the default to nil group.expects(:set_members).with(['user1', 'user2'], nil).in_sequence(create) provider.create end it 'should not create a group if a user by the same name exists' do Puppet::Util::Windows::ADSI::Group.expects(:create).with('testers').raises( Puppet::Error.new("Cannot create group if user 'testers' exists.") ) expect{ provider.create }.to raise_error( Puppet::Error, /Cannot create group if user 'testers' exists./ ) end it 'should commit a newly created group' do provider.group.expects( :commit ) provider.flush end end it "should be able to test whether a group exists" do Puppet::Util::Windows::ADSI.stubs(:sid_uri_safe).returns(nil) Puppet::Util::Windows::ADSI.stubs(:connect).returns stub('connection') provider.should be_exists Puppet::Util::Windows::ADSI.stubs(:connect).returns nil provider.should_not be_exists end it "should be able to delete a group" do connection.expects(:Delete).with('group', 'testers') provider.delete end it "should report the group's SID as gid" do Puppet::Util::Windows::SID.expects(:name_to_sid).with('testers').returns('S-1-5-32-547') provider.gid.should == 'S-1-5-32-547' end it "should fail when trying to manage the gid property" do provider.expects(:fail).with { |msg| msg =~ /gid is read-only/ } provider.send(:gid=, 500) end it "should prefer the domain component from the resolved SID" do provider.members_to_s(['.\Administrators']).should == 'BUILTIN\Administrators' end end diff --git a/spec/unit/provider/ldap_spec.rb b/spec/unit/provider/ldap_spec.rb index 6582f6b21..18adc6421 100755 --- a/spec/unit/provider/ldap_spec.rb +++ b/spec/unit/provider/ldap_spec.rb @@ -1,244 +1,244 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/provider/ldap' describe Puppet::Provider::Ldap do before do @class = Class.new(Puppet::Provider::Ldap) end it "should be able to define its manager" do manager = mock 'manager' Puppet::Util::Ldap::Manager.expects(:new).returns manager @class.stubs :mk_resource_methods manager.expects(:manages).with(:one) @class.manages(:one).should equal(manager) @class.manager.should equal(manager) end it "should be able to prefetch instances from ldap" do @class.should respond_to(:prefetch) end it "should create its resource getter/setter methods when the manager is defined" do manager = mock 'manager' Puppet::Util::Ldap::Manager.expects(:new).returns manager @class.expects :mk_resource_methods manager.stubs(:manages) @class.manages(:one).should equal(manager) end it "should have an instances method" do @class.should respond_to(:instances) end describe "when providing a list of instances" do it "should convert all results returned from the manager's :search method into provider instances" do manager = mock 'manager' @class.stubs(:manager).returns manager manager.expects(:search).returns %w{one two three} @class.expects(:new).with("one").returns(1) @class.expects(:new).with("two").returns(2) @class.expects(:new).with("three").returns(3) @class.instances.should == [1,2,3] end end it "should have a prefetch method" do @class.should respond_to(:prefetch) end describe "when prefetching" do before do @manager = mock 'manager' @class.stubs(:manager).returns @manager @resource = mock 'resource' @resources = {"one" => @resource} end it "should find an entry for each passed resource" do @manager.expects(:find).with("one").returns nil @class.stubs(:new) @resource.stubs(:provider=) @class.prefetch(@resources) end describe "resources that do not exist" do it "should create a provider with :ensure => :absent" do result = mock 'result' @manager.expects(:find).with("one").returns nil @class.expects(:new).with(:ensure => :absent).returns "myprovider" @resource.expects(:provider=).with("myprovider") @class.prefetch(@resources) end end describe "resources that exist" do it "should create a provider with the results of the find" do @manager.expects(:find).with("one").returns("one" => "two") @class.expects(:new).with("one" => "two", :ensure => :present).returns "myprovider" @resource.expects(:provider=).with("myprovider") @class.prefetch(@resources) end it "should set :ensure to :present in the returned values" do @manager.expects(:find).with("one").returns("one" => "two") @class.expects(:new).with("one" => "two", :ensure => :present).returns "myprovider" @resource.expects(:provider=).with("myprovider") @class.prefetch(@resources) end end end describe "when being initialized" do it "should fail if no manager has been defined" do lambda { @class.new }.should raise_error(Puppet::DevError) end it "should fail if the manager is invalid" do manager = stub "manager", :valid? => false @class.stubs(:manager).returns manager lambda { @class.new }.should raise_error(Puppet::DevError) end describe "with a hash" do before do @manager = stub "manager", :valid? => true @class.stubs(:manager).returns @manager @resource_class = mock 'resource_class' @class.stubs(:resource_type).returns @resource_class @property_class = stub 'property_class', :array_matching => :all, :superclass => Puppet::Property @resource_class.stubs(:attrclass).with(:one).returns(@property_class) @resource_class.stubs(:valid_parameter?).returns true end it "should store a copy of the hash as its ldap_properties" do instance = @class.new(:one => :two) instance.ldap_properties.should == {:one => :two} end it "should only store the first value of each value array for those attributes that do not match all values" do @property_class.expects(:array_matching).returns :first instance = @class.new(:one => %w{two three}) instance.properties.should == {:one => "two"} end it "should store the whole value array for those attributes that match all values" do @property_class.expects(:array_matching).returns :all instance = @class.new(:one => %w{two three}) instance.properties.should == {:one => %w{two three}} end it "should only use the first value for attributes that are not properties" do # Yay. hackish, but easier than mocking everything. @resource_class.expects(:attrclass).with(:a).returns Puppet::Type.type(:user).attrclass(:name) @property_class.stubs(:array_matching).returns :all instance = @class.new(:one => %w{two three}, :a => %w{b c}) instance.properties.should == {:one => %w{two three}, :a => "b"} end it "should discard any properties not valid in the resource class" do @resource_class.expects(:valid_parameter?).with(:a).returns false @property_class.stubs(:array_matching).returns :all instance = @class.new(:one => %w{two three}, :a => %w{b}) instance.properties.should == {:one => %w{two three}} end end end describe "when an instance" do before do @manager = stub "manager", :valid? => true @class.stubs(:manager).returns @manager @instance = @class.new @property_class = stub 'property_class', :array_matching => :all, :superclass => Puppet::Property @resource_class = stub 'resource_class', :attrclass => @property_class, :valid_parameter? => true, :validproperties => [:one, :two] @class.stubs(:resource_type).returns @resource_class end it "should have a method for creating the ldap entry" do @instance.should respond_to(:create) end it "should have a method for removing the ldap entry" do @instance.should respond_to(:delete) end it "should have a method for returning the class's manager" do @instance.manager.should equal(@manager) end it "should indicate when the ldap entry already exists" do @instance = @class.new(:ensure => :present) - @instance.exists?.should be_true + @instance.exists?.should be_truthy end it "should indicate when the ldap entry does not exist" do @instance = @class.new(:ensure => :absent) - @instance.exists?.should be_false + @instance.exists?.should be_falsey end describe "is being flushed" do it "should call the manager's :update method with its name, current attributes, and desired attributes" do @instance.stubs(:name).returns "myname" @instance.stubs(:ldap_properties).returns(:one => :two) @instance.stubs(:properties).returns(:three => :four) @manager.expects(:update).with(@instance.name, {:one => :two}, {:three => :four}) @instance.flush end end describe "is being created" do before do @rclass = mock 'resource_class' @rclass.stubs(:validproperties).returns([:one, :two]) @resource = mock 'resource' @resource.stubs(:class).returns @rclass @resource.stubs(:should).returns nil @instance.stubs(:resource).returns @resource end it "should set its :ensure value to :present" do @instance.create @instance.properties[:ensure].should == :present end it "should set all of the other attributes from the resource" do @resource.expects(:should).with(:one).returns "oneval" @resource.expects(:should).with(:two).returns "twoval" @instance.create @instance.properties[:one].should == "oneval" @instance.properties[:two].should == "twoval" end end describe "is being deleted" do it "should set its :ensure value to :absent" do @instance.delete @instance.properties[:ensure].should == :absent end end end end diff --git a/spec/unit/provider/naginator_spec.rb b/spec/unit/provider/naginator_spec.rb index d3f89e290..80b2dfc05 100755 --- a/spec/unit/provider/naginator_spec.rb +++ b/spec/unit/provider/naginator_spec.rb @@ -1,79 +1,79 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/provider/naginator' describe Puppet::Provider::Naginator do before do @resource_type = stub 'resource_type', :name => :nagios_test @class = Class.new(Puppet::Provider::Naginator) @class.stubs(:resource_type).returns @resource_type end it "should be able to look up the associated Nagios type" do nagios_type = mock "nagios_type" nagios_type.stubs :attr_accessor Nagios::Base.expects(:type).with(:test).returns nagios_type @class.nagios_type.should equal(nagios_type) end it "should use the Nagios type to determine whether an attribute is valid" do nagios_type = mock "nagios_type" nagios_type.stubs :attr_accessor Nagios::Base.expects(:type).with(:test).returns nagios_type nagios_type.expects(:parameters).returns [:foo, :bar] - @class.valid_attr?(:test, :foo).should be_true + @class.valid_attr?(:test, :foo).should be_truthy end it "should use Naginator to parse configuration snippets" do parser = mock 'parser' parser.expects(:parse).with("my text").returns "my instances" Nagios::Parser.expects(:new).returns(parser) @class.parse("my text").should == "my instances" end it "should join Nagios::Base records with '\\n' when asked to convert them to text" do @class.expects(:header).returns "myheader\n" @class.to_file([:one, :two]).should == "myheader\none\ntwo" end it "should be able to prefetch instance from configuration files" do @class.should respond_to(:prefetch) end it "should be able to generate a list of instances" do @class.should respond_to(:instances) end it "should never skip records" do @class.should_not be_skip_record("foo") end end describe Nagios::Base do it "should not turn set parameters into arrays #17871" do obj = Nagios::Base.create('host') obj.host_name = "my_hostname" obj.host_name.should == "my_hostname" end end describe Nagios::Parser do include PuppetSpec::Files subject do described_class.new end let(:config) { File.new( my_fixture('define_empty_param') ).read } it "should handle empty parameter values" do expect { subject.parse(config) }.to_not raise_error end end diff --git a/spec/unit/provider/network_device_spec.rb b/spec/unit/provider/network_device_spec.rb index 683f7f9cf..b8d305640 100755 --- a/spec/unit/provider/network_device_spec.rb +++ b/spec/unit/provider/network_device_spec.rb @@ -1,152 +1,152 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/provider/network_device' require 'ostruct' Puppet::Type.type(:vlan).provide :test, :parent => Puppet::Provider::NetworkDevice do mk_resource_methods def self.lookup(device, name) end def self.device(url) :device end end provider_class = Puppet::Type.type(:vlan).provider(:test) describe provider_class do before do @resource = stub("resource", :name => "test") @provider = provider_class.new(@resource) end it "should be able to prefetch instances from the device" do provider_class.should respond_to(:prefetch) end it "should have an instances method" do provider_class.should respond_to(:instances) end describe "when prefetching" do before do @resource = stub_everything 'resource' @resources = {"200" => @resource} provider_class.stubs(:lookup) end it "should lookup an entry for each passed resource" do provider_class.expects(:lookup).with{ |device,value| value == "200" }.returns nil provider_class.stubs(:new) @resource.stubs(:provider=) provider_class.prefetch(@resources) end describe "resources that do not exist" do it "should create a provider with :ensure => :absent" do provider_class.stubs(:lookup).returns(nil) provider_class.expects(:new).with(:device, :ensure => :absent).returns "myprovider" @resource.expects(:provider=).with("myprovider") provider_class.prefetch(@resources) end end describe "resources that exist" do it "should create a provider with the results of the find and ensure at present" do provider_class.stubs(:lookup).returns({ :name => "200", :description => "myvlan"}) provider_class.expects(:new).with(:device, :name => "200", :description => "myvlan", :ensure => :present).returns "myprovider" @resource.expects(:provider=).with("myprovider") provider_class.prefetch(@resources) end end end describe "when being initialized" do describe "with a hash" do before do @resource_class = mock 'resource_class' provider_class.stubs(:resource_type).returns @resource_class @property_class = stub 'property_class', :array_matching => :all, :superclass => Puppet::Property @resource_class.stubs(:attrclass).with(:one).returns(@property_class) @resource_class.stubs(:valid_parameter?).returns true end it "should store a copy of the hash as its vlan_properties" do instance = provider_class.new(:device, :one => :two) instance.former_properties.should == {:one => :two} end end end describe "when an instance" do before do @instance = provider_class.new(:device) @property_class = stub 'property_class', :array_matching => :all, :superclass => Puppet::Property @resource_class = stub 'resource_class', :attrclass => @property_class, :valid_parameter? => true, :validproperties => [:description] provider_class.stubs(:resource_type).returns @resource_class end it "should have a method for creating the instance" do @instance.should respond_to(:create) end it "should have a method for removing the instance" do @instance.should respond_to(:destroy) end it "should indicate when the instance already exists" do @instance = provider_class.new(:device, :ensure => :present) - @instance.exists?.should be_true + @instance.exists?.should be_truthy end it "should indicate when the instance does not exist" do @instance = provider_class.new(:device, :ensure => :absent) - @instance.exists?.should be_false + @instance.exists?.should be_falsey end describe "is being flushed" do it "should flush properties" do @instance = provider_class.new(:ensure => :present, :name => "200", :description => "myvlan") @instance.flush @instance.properties.should be_empty end end describe "is being created" do before do @rclass = mock 'resource_class' @rclass.stubs(:validproperties).returns([:description]) @resource = stub_everything 'resource' @resource.stubs(:class).returns @rclass @resource.stubs(:should).returns nil @instance.stubs(:resource).returns @resource end it "should set its :ensure value to :present" do @instance.create @instance.properties[:ensure].should == :present end it "should set all of the other attributes from the resource" do @resource.expects(:should).with(:description).returns "myvlan" @instance.create @instance.properties[:description].should == "myvlan" end end describe "is being destroyed" do it "should set its :ensure value to :absent" do @instance.destroy @instance.properties[:ensure].should == :absent end end end end diff --git a/spec/unit/provider/package/up2date_spec.rb b/spec/unit/provider/package/up2date_spec.rb index 01c19d4a2..99316fbdc 100644 --- a/spec/unit/provider/package/up2date_spec.rb +++ b/spec/unit/provider/package/up2date_spec.rb @@ -1,24 +1,24 @@ # spec/unit/provider/package/up2date_spec.rb require 'spec_helper' describe 'up2date package provider' do # This sets the class itself as the subject rather than # an instance of the class. subject do Puppet::Type.type(:package).provider(:up2date) end osfamilies = [ 'redhat' ] releases = [ '2.1', '3', '4' ] osfamilies.each do |osfamily| releases.each do |release| it "should be the default provider on #{osfamily} #{release}" do Facter.expects(:value).with(:osfamily).returns(osfamily) Facter.expects(:value).with(:lsbdistrelease).returns(release) - subject.default?.should be_true + subject.default?.should be_truthy end end end end diff --git a/spec/unit/provider/package/windows/exe_package_spec.rb b/spec/unit/provider/package/windows/exe_package_spec.rb index 89e101ccd..e7b354943 100644 --- a/spec/unit/provider/package/windows/exe_package_spec.rb +++ b/spec/unit/provider/package/windows/exe_package_spec.rb @@ -1,99 +1,99 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/provider/package/windows/exe_package' describe Puppet::Provider::Package::Windows::ExePackage do subject { described_class } let (:name) { 'Git version 1.7.11' } let (:version) { '1.7.11' } let (:source) { 'E:\Git-1.7.11.exe' } let (:uninstall) { '"C:\Program Files (x86)\Git\unins000.exe" /SP-' } context '::from_registry' do it 'should return an instance of ExePackage' do subject.expects(:valid?).returns(true) pkg = subject.from_registry('', {'DisplayName' => name, 'DisplayVersion' => version, 'UninstallString' => uninstall}) pkg.name.should == name pkg.version.should == version pkg.uninstall_string.should == uninstall end it 'should return nil if it is not a valid executable' do subject.expects(:valid?).returns(false) subject.from_registry('', {}).should be_nil end end context '::valid?' do let(:name) { 'myproduct' } let(:values) do { 'DisplayName' => name, 'UninstallString' => uninstall } end { 'DisplayName' => ['My App', ''], 'UninstallString' => ['E:\uninstall.exe', ''], 'SystemComponent' => [nil, 1], 'WindowsInstaller' => [nil, 1], 'ParentKeyName' => [nil, 'Uber Product'], 'Security Update' => [nil, 'KB890830'], 'Update Rollup' => [nil, 'Service Pack 42'], 'Hotfix' => [nil, 'QFE 42'] }.each_pair do |k, arr| it "should accept '#{k}' with value '#{arr[0]}'" do values[k] = arr[0] - subject.valid?(name, values).should be_true + subject.valid?(name, values).should be_truthy end it "should reject '#{k}' with value '#{arr[1]}'" do values[k] = arr[1] - subject.valid?(name, values).should be_false + subject.valid?(name, values).should be_falsey end end it 'should reject packages whose name starts with "KBXXXXXX"' do - subject.valid?('KB890830', values).should be_false + subject.valid?('KB890830', values).should be_falsey end it 'should accept packages whose name does not start with "KBXXXXXX"' do - subject.valid?('My Update (KB890830)', values).should be_true + subject.valid?('My Update (KB890830)', values).should be_truthy end end context '#match?' do let(:pkg) { subject.new(name, version, uninstall) } it 'should match product name' do - pkg.match?({:name => name}).should be_true + pkg.match?({:name => name}).should be_truthy end it 'should return false otherwise' do - pkg.match?({:name => 'not going to find it'}).should be_false + pkg.match?({:name => 'not going to find it'}).should be_falsey end end context '#install_command' do it 'should install using the source' do cmd = subject.install_command({:source => source}) cmd.should == ['cmd.exe', '/c', 'start', '"puppet-install"', '/w', source] end end context '#uninstall_command' do ['C:\uninstall.exe', 'C:\Program Files\uninstall.exe'].each do |exe| it "should quote #{exe}" do subject.new(name, version, exe).uninstall_command.should == ['cmd.exe', '/c', 'start', '"puppet-uninstall"', '/w', "\"#{exe}\""] end end ['"C:\Program Files\uninstall.exe"', '"C:\Program Files (x86)\Git\unins000.exe" /SILENT"'].each do |exe| it "should not quote #{exe}" do subject.new(name, version, exe).uninstall_command.should == ['cmd.exe', '/c', 'start', '"puppet-uninstall"', '/w', exe] end end end end diff --git a/spec/unit/provider/package/windows/msi_package_spec.rb b/spec/unit/provider/package/windows/msi_package_spec.rb index 40a323ace..79f0b9223 100644 --- a/spec/unit/provider/package/windows/msi_package_spec.rb +++ b/spec/unit/provider/package/windows/msi_package_spec.rb @@ -1,115 +1,115 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/provider/package/windows/msi_package' describe Puppet::Provider::Package::Windows::MsiPackage do subject { described_class } let (:name) { 'mysql-5.1.58-win-x64' } let (:version) { '5.1.58' } let (:source) { 'E:\mysql-5.1.58-win-x64.msi' } let (:productcode) { '{E437FFB6-5C49-4DAC-ABAE-33FF065FE7CC}' } let (:packagecode) { '{5A6FD560-763A-4BC1-9E03-B18DFFB7C72C}' } def expect_installer inst = mock inst.expects(:ProductState).returns(5) inst.expects(:ProductInfo).with(productcode, 'PackageCode').returns(packagecode) subject.expects(:installer).returns(inst) end context '::installer', :if => Puppet.features.microsoft_windows? do it 'should return an instance of the COM interface' do subject.installer.should_not be_nil end end context '::from_registry' do it 'should return an instance of MsiPackage' do subject.expects(:valid?).returns(true) expect_installer pkg = subject.from_registry(productcode, {'DisplayName' => name, 'DisplayVersion' => version}) pkg.name.should == name pkg.version.should == version pkg.productcode.should == productcode pkg.packagecode.should == packagecode end it 'should return nil if it is not a valid MSI' do subject.expects(:valid?).returns(false) subject.from_registry(productcode, {}).should be_nil end end context '::valid?' do let(:values) do { 'DisplayName' => name, 'DisplayVersion' => version, 'WindowsInstaller' => 1 } end { 'DisplayName' => ['My App', ''], 'SystemComponent' => [nil, 1], 'WindowsInstaller' => [1, nil], }.each_pair do |k, arr| it "should accept '#{k}' with value '#{arr[0]}'" do values[k] = arr[0] - subject.valid?(productcode, values).should be_true + subject.valid?(productcode, values).should be_truthy end it "should reject '#{k}' with value '#{arr[1]}'" do values[k] = arr[1] - subject.valid?(productcode, values).should be_false + subject.valid?(productcode, values).should be_falsey end end it 'should reject packages whose name is not a productcode' do - subject.valid?('AddressBook', values).should be_false + subject.valid?('AddressBook', values).should be_falsey end it 'should accept packages whose name is a productcode' do - subject.valid?(productcode, values).should be_true + subject.valid?(productcode, values).should be_truthy end end context '#match?' do it 'should match package codes case-insensitively' do pkg = subject.new(name, version, productcode, packagecode.upcase) - pkg.match?({:name => packagecode.downcase}).should be_true + pkg.match?({:name => packagecode.downcase}).should be_truthy end it 'should match product codes case-insensitively' do pkg = subject.new(name, version, productcode.upcase, packagecode) - pkg.match?({:name => productcode.downcase}).should be_true + pkg.match?({:name => productcode.downcase}).should be_truthy end it 'should match product name' do pkg = subject.new(name, version, productcode, packagecode) - pkg.match?({:name => name}).should be_true + pkg.match?({:name => name}).should be_truthy end it 'should return false otherwise' do pkg = subject.new(name, version, productcode, packagecode) - pkg.match?({:name => 'not going to find it'}).should be_false + pkg.match?({:name => 'not going to find it'}).should be_falsey end end context '#install_command' do it 'should install using the source' do cmd = subject.install_command({:source => source}) cmd.should == ['msiexec.exe', '/qn', '/norestart', '/i', source] end end context '#uninstall_command' do it 'should uninstall using the productcode' do pkg = subject.new(name, version, productcode, packagecode) pkg.uninstall_command.should == ['msiexec.exe', '/qn', '/norestart', '/x', productcode] end end end diff --git a/spec/unit/provider/service/openbsd_spec.rb b/spec/unit/provider/service/openbsd_spec.rb index 777c348a7..55e9a2e43 100755 --- a/spec/unit/provider/service/openbsd_spec.rb +++ b/spec/unit/provider/service/openbsd_spec.rb @@ -1,201 +1,201 @@ #!/usr/bin/env ruby # # Unit testing for the OpenBSD service provider require 'spec_helper' provider_class = Puppet::Type.type(:service).provider(:openbsd) describe provider_class, :unless => Puppet.features.microsoft_windows? do before :each do Puppet::Type.type(:service).stubs(:defaultprovider).returns described_class Facter.stubs(:value).with(:operatingsystem).returns :openbsd FileTest.stubs(:file?).with('/usr/sbin/rcctl').returns true FileTest.stubs(:executable?).with('/usr/sbin/rcctl').returns true end describe "#instances" do it "should have an instances method" do described_class.should respond_to :instances end it "should list all available services" do described_class.stubs(:execpipe).with(['/usr/sbin/rcctl', :status]).yields File.read(my_fixture('rcctl_status')) described_class.instances.map(&:name).should == [ 'accounting', 'pf', 'postgresql', 'tftpd', 'wsmoused', 'xdm', ] 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, :override_locale => false, :squelch => false, :combine => true) provider.start end it "should start the service otherwise" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd')) provider.expects(:texecute).with(:start, ['/usr/sbin/rcctl', '-f', :start, 'sshd'], 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, :override_locale => false, :squelch => false, :combine => true) provider.stop end it "should stop the service otherwise" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd')) provider.expects(:texecute).with(:stop, ['/usr/sbin/rcctl', :stop, 'sshd'], true) provider.stop end end describe "#status" 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(['/usr/sbin/rcctl', :status, 'sshd'], :failonfail => true, :override_locale => false, :squelch => false, :combine => true).never provider.expects(:execute).with(['/bin/foo'], :failonfail => false, :override_locale => false, :squelch => false, :combine => 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(['/usr/sbin/rcctl', :status, 'sshd'], :failonfail => true, :override_locale => false, :squelch => false, :combine => true).never provider.expects(:execute).with(['/bin/foo'], :failonfail => false, :override_locale => false, :squelch => false, :combine => 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(['/usr/sbin/rcctl', :status, 'sshd'], :failonfail => true, :override_locale => false, :squelch => false, :combine => true).never provider.expects(:execute).with(['/bin/foo'], :failonfail => false, :override_locale => false, :squelch => false, :combine => true) $CHILD_STATUS.stubs(:exitstatus).returns 0 provider.status.should == :running 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(['/usr/sbin/rcctl', '-f', :restart, 'sshd'], :failonfail => true, :override_locale => false, :squelch => false, :combine => true).never provider.expects(:execute).with(['/bin/foo'], :failonfail => true, :override_locale => false, :squelch => false, :combine => true) provider.restart end it "should restart the service with rcctl restart if hasrestart is true" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :hasrestart => true)) provider.expects(:texecute).with(:restart, ['/usr/sbin/rcctl', '-f', :restart, 'sshd'], true) provider.restart end it "should restart the service with rcctl stop/start if hasrestart is false" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :hasrestart => false)) provider.expects(:texecute).with(:restart, ['/usr/sbin/rcctl', '-f', :restart, 'sshd'], true).never provider.expects(:texecute).with(:stop, ['/usr/sbin/rcctl', :stop, 'sshd'], true) provider.expects(:texecute).with(:start, ['/usr/sbin/rcctl', '-f', :start, 'sshd'], true) provider.restart end end describe "#enabled?" do it "should return :true if the service is enabled" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd')) described_class.stubs(:rcctl).with('status', 'sshd').returns('-6') provider.expects(:execute).with(['/usr/sbin/rcctl', 'status', 'sshd'], :failonfail => false, :combine => false, :squelch => false).returns('-6') provider.enabled?.should == :true end it "should return :false if the service is disabled" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd')) described_class.stubs(:rcctl).with('status', 'sshd').returns('NO') provider.expects(:execute).with(['/usr/sbin/rcctl', 'status', 'sshd'], :failonfail => false, :combine => false, :squelch => false).returns('NO') provider.enabled?.should == :false end end describe "#enable" do it "should run rcctl enable to enable the service" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd')) described_class.stubs(:rcctl).with(:enable, 'sshd').returns('') provider.expects(:rcctl).with(:enable, 'sshd') provider.enable end it "should run rcctl enable with flags if provided" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :flags => '-6')) described_class.stubs(:rcctl).with(:enable, 'sshd', :flags, '-6').returns('') provider.expects(:rcctl).with(:enable, 'sshd', :flags, '-6') provider.enable end end describe "#disable" do it "should run rcctl disable to disable the service" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd')) described_class.stubs(:rcctl).with(:disable, 'sshd').returns('') provider.expects(:rcctl).with(:disable, 'sshd') provider.disable end end describe "#running?" do it "should run rcctl check to check the service" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd')) described_class.stubs(:rcctl).with(:check, 'sshd').returns('sshd(ok)') provider.expects(:execute).with(['/usr/sbin/rcctl', 'check', 'sshd'], :failonfail => false, :combine => false, :squelch => false).returns('sshd(ok)') - provider.running?.should be_true + provider.running?.should be_truthy end it "should return true if the service is running" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd')) described_class.stubs(:rcctl).with(:check, 'sshd').returns('sshd(ok)') provider.expects(:execute).with(['/usr/sbin/rcctl', 'check', 'sshd'], :failonfail => false, :combine => false, :squelch => false).returns('sshd(ok)') - provider.running?.should be_true + provider.running?.should be_truthy end it "should return nil if the service is not running" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd')) described_class.stubs(:rcctl).with(:check, 'sshd').returns('sshd(failed)') provider.expects(:execute).with(['/usr/sbin/rcctl', 'check', 'sshd'], :failonfail => false, :combine => false, :squelch => false).returns('sshd(failed)') provider.running?.should be_nil end end describe "#flags" do it "should return flags when set" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :flags => '-6')) described_class.stubs(:rcctl).with(:status, 'sshd').returns('-6') provider.expects(:execute).with(['/usr/sbin/rcctl', 'status', 'sshd'], :failonfail => false, :combine => false, :squelch => false).returns('-6') provider.flags end it "should return empty flags" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd')) described_class.stubs(:rcctl).with(:status, 'sshd').returns('') provider.expects(:execute).with(['/usr/sbin/rcctl', 'status', 'sshd'], :failonfail => false, :combine => false, :squelch => false).returns('') provider.flags end it "should return flags for special services" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'pf')) described_class.stubs(:rcctl).with(:status, 'pf').returns('YES') provider.expects(:execute).with(['/usr/sbin/rcctl', 'status', 'pf'], :failonfail => false, :combine => false, :squelch => false).returns('YES') provider.flags end end describe "#flags=" do it "should run rcctl to set flags" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd')) described_class.stubs(:rcctl).with(:enable, 'sshd', :flags, '-4').returns('') provider.expects(:rcctl).with(:enable, 'sshd', :flags, '-4') provider.flags = '-4' end end end diff --git a/spec/unit/provider/service/openwrt_spec.rb b/spec/unit/provider/service/openwrt_spec.rb index 4550d6623..a64a9f496 100755 --- a/spec/unit/provider/service/openwrt_spec.rb +++ b/spec/unit/provider/service/openwrt_spec.rb @@ -1,109 +1,109 @@ #! /usr/bin/env ruby # # Unit testing for the OpenWrt service Provider # require 'spec_helper' describe Puppet::Type.type(:service).provider(:openwrt), :if => Puppet.features.posix? do let(:resource) do resource = stub 'resource' resource.stubs(:[]).returns(nil) resource.stubs(:[]).with(:name).returns "myservice" resource.stubs(:[]).with(:path).returns ["/etc/init.d"] resource end let(:provider) do provider = described_class.new provider.stubs(:get).with(:hasstatus).returns false provider end before :each do resource.stubs(:provider).returns provider provider.resource = resource FileTest.stubs(:file?).with('/etc/rc.common').returns true FileTest.stubs(:executable?).with('/etc/rc.common').returns true # All OpenWrt tests operate on the init script directly. It must exist. File.stubs(:directory?).with('/etc/init.d').returns true Puppet::FileSystem.stubs(:exist?).with('/etc/init.d/myservice').returns true FileTest.stubs(:file?).with('/etc/init.d/myservice').returns true FileTest.stubs(:executable?).with('/etc/init.d/myservice').returns true end operatingsystem = 'openwrt' it "should be the default provider on #{operatingsystem}" do Facter.expects(:value).with(:operatingsystem).returns(operatingsystem) - described_class.default?.should be_true + described_class.default?.should be_truthy end # test self.instances describe "when getting all service instances" do let(:services) {['dnsmasq', 'dropbear', 'firewall', 'led', 'puppet', 'uhttpd' ]} before :each do Dir.stubs(:entries).returns services FileTest.stubs(:directory?).returns(true) FileTest.stubs(:executable?).returns(true) end it "should return instances for all services" do services.each do |inst| described_class.expects(:new).with{|hash| hash[:name] == inst && hash[:path] == '/etc/init.d'}.returns("#{inst}_instance") end results = services.collect {|x| "#{x}_instance"} described_class.instances.should == results end end it "should have an enabled? method" do provider.should respond_to(:enabled?) end it "should have an enable method" do provider.should respond_to(:enable) end it "should have a disable method" do provider.should respond_to(:disable) end [:start, :stop, :restart].each do |method| it "should have a #{method} method" do provider.should respond_to(method) end describe "when running #{method}" do it "should use any provided explicit command" do resource.stubs(:[]).with(method).returns "/user/specified/command" provider.expects(:execute).with { |command, *args| command == ["/user/specified/command"] } provider.send(method) end it "should execute the init script with #{method} when no explicit command is provided" do resource.stubs(:[]).with("has#{method}".intern).returns :true provider.expects(:execute).with { |command, *args| command == ['/etc/init.d/myservice', method ]} provider.send(method) end end end describe "when checking status" do it "should consider the service :running if it has a pid" do provider.expects(:getpid).returns "1234" provider.status.should == :running end it "should consider the service :stopped if it doesn't have a pid" do provider.expects(:getpid).returns nil provider.status.should == :stopped end end end diff --git a/spec/unit/provider/service/redhat_spec.rb b/spec/unit/provider/service/redhat_spec.rb index 9478cd83b..cdc0e867c 100755 --- a/spec/unit/provider/service/redhat_spec.rb +++ b/spec/unit/provider/service/redhat_spec.rb @@ -1,163 +1,163 @@ #! /usr/bin/env ruby # # Unit testing for the RedHat service Provider # require 'spec_helper' provider_class = Puppet::Type.type(:service).provider(:redhat) describe provider_class, :if => Puppet.features.posix? do before :each do @class = Puppet::Type.type(:service).provider(:redhat) @resource = stub 'resource' @resource.stubs(:[]).returns(nil) @resource.stubs(:[]).with(:name).returns "myservice" @provider = provider_class.new @resource.stubs(:provider).returns @provider @provider.resource = @resource @provider.stubs(:get).with(:hasstatus).returns false FileTest.stubs(:file?).with('/sbin/service').returns true FileTest.stubs(:executable?).with('/sbin/service').returns true Facter.stubs(:value).with(:operatingsystem).returns('CentOS') end osfamily = [ 'RedHat', 'Suse' ] osfamily.each do |osfamily| it "should be the default provider on #{osfamily}" do Facter.expects(:value).with(:osfamily).returns(osfamily) - provider_class.default?.should be_true + provider_class.default?.should be_truthy end end # test self.instances describe "when getting all service instances" do before :each do @services = ['one', 'two', 'three', 'four', 'kudzu', 'functions', 'halt', 'killall', 'single', 'linuxconf', 'boot', 'reboot'] @not_services = ['functions', 'halt', 'killall', 'single', 'linuxconf', 'reboot', 'boot'] Dir.stubs(:entries).returns @services FileTest.stubs(:directory?).returns(true) FileTest.stubs(:executable?).returns(true) end it "should return instances for all services" do (@services-@not_services).each do |inst| @class.expects(:new).with{|hash| hash[:name] == inst && hash[:path] == '/etc/init.d'}.returns("#{inst}_instance") end results = (@services-@not_services).collect {|x| "#{x}_instance"} @class.instances.should == results end it "should call service status when initialized from provider" do @resource.stubs(:[]).with(:status).returns nil @provider.stubs(:get).with(:hasstatus).returns true @provider.expects(:execute).with{|command, *args| command == ['/sbin/service', 'myservice', 'status']} @provider.send(:status) end end it "should use '--add' and 'on' when calling enable" do provider_class.expects(:chkconfig).with("--add", @resource[:name]) provider_class.expects(:chkconfig).with(@resource[:name], :on) @provider.enable end it "(#15797) should explicitly turn off the service in all run levels" do provider_class.expects(:chkconfig).with("--level", "0123456", @resource[:name], :off) @provider.disable end it "should have an enabled? method" do @provider.should respond_to(:enabled?) end describe "when checking enabled? on Suse" do before :each do Facter.expects(:value).with(:osfamily).returns 'Suse' end it "should check for on" do provider_class.stubs(:chkconfig).with(@resource[:name]).returns "#{@resource[:name]} on" @provider.enabled?.should == :true end it "should check for off" do provider_class.stubs(:chkconfig).with(@resource[:name]).returns "#{@resource[:name]} off" @provider.enabled?.should == :false end it "should check for unknown service" do provider_class.stubs(:chkconfig).with(@resource[:name]).returns "#{@resource[:name]}: unknown service" @provider.enabled?.should == :false end end it "should have an enable method" do @provider.should respond_to(:enable) end it "should have a disable method" do @provider.should respond_to(:disable) end [:start, :stop, :status, :restart].each do |method| it "should have a #{method} method" do @provider.should respond_to(method) end describe "when running #{method}" do it "should use any provided explicit command" do @resource.stubs(:[]).with(method).returns "/user/specified/command" @provider.expects(:execute).with { |command, *args| command == ["/user/specified/command"] } @provider.send(method) end it "should execute the service script with #{method} when no explicit command is provided" do @resource.stubs(:[]).with("has#{method}".intern).returns :true @provider.expects(:execute).with { |command, *args| command == ['/sbin/service', 'myservice', method.to_s]} @provider.send(method) end end end describe "when checking status" do describe "when hasstatus is :true" do before :each do @resource.stubs(:[]).with(:hasstatus).returns :true end it "should execute the service script with fail_on_failure false" do @provider.expects(:texecute).with(:status, ['/sbin/service', 'myservice', 'status'], false) @provider.status end it "should consider the process running if the command returns 0" do @provider.expects(:texecute).with(:status, ['/sbin/service', 'myservice', 'status'], false) $CHILD_STATUS.stubs(:exitstatus).returns(0) @provider.status.should == :running end [-10,-1,1,10].each { |ec| it "should consider the process stopped if the command returns something non-0" do @provider.expects(:texecute).with(:status, ['/sbin/service', 'myservice', 'status'], false) $CHILD_STATUS.stubs(:exitstatus).returns(ec) @provider.status.should == :stopped end } end describe "when hasstatus is not :true" do it "should consider the service :running if it has a pid" do @provider.expects(:getpid).returns "1234" @provider.status.should == :running end it "should consider the service :stopped if it doesn't have a pid" do @provider.expects(:getpid).returns nil @provider.status.should == :stopped end end end describe "when restarting and hasrestart is not :true" do it "should stop and restart the process with the server script" do @provider.expects(:texecute).with(:stop, ['/sbin/service', 'myservice', 'stop'], true) @provider.expects(:texecute).with(:start, ['/sbin/service', 'myservice', 'start'], true) @provider.restart end end end diff --git a/spec/unit/provider/service/systemd_spec.rb b/spec/unit/provider/service/systemd_spec.rb index f2b95cdfd..26c9de4d8 100755 --- a/spec/unit/provider/service/systemd_spec.rb +++ b/spec/unit/provider/service/systemd_spec.rb @@ -1,176 +1,176 @@ #! /usr/bin/env ruby # # Unit testing for the systemd service Provider # require 'spec_helper' describe Puppet::Type.type(:service).provider(:systemd) do before :each do Puppet::Type.type(:service).stubs(:defaultprovider).returns described_class described_class.stubs(:which).with('systemctl').returns '/bin/systemctl' end let :provider do described_class.new(:name => 'sshd.service') end osfamily = [ 'archlinux' ] osfamily.each do |osfamily| it "should be the default provider on #{osfamily}" do Facter.expects(:value).with(:osfamily).returns(osfamily) - described_class.default?.should be_true + described_class.default?.should be_truthy end end it "should be the default provider on rhel7" do Facter.expects(:value).with(:osfamily).at_least_once.returns(:redhat) Facter.expects(:value).with(:operatingsystemmajrelease).at_least_once.returns("7") - described_class.default?.should be_true + described_class.default?.should be_truthy end it "should not be the default provider on rhel6" do Facter.expects(:value).with(:osfamily).at_least_once.returns(:redhat) Facter.expects(:value).with(:operatingsystemmajrelease).at_least_once.returns("6") - described_class.default?.should_not be_true + described_class.default?.should_not be_truthy end it "should be the default provider on sles12" do Facter.expects(:value).with(:osfamily).at_least_once.returns(:suse) Facter.expects(:value).with(:operatingsystemmajrelease).at_least_once.returns("12") - described_class.default?.should be_true + described_class.default?.should be_truthy end it "should be the default provider on opensuse13" do Facter.expects(:value).with(:osfamily).at_least_once.returns(:suse) Facter.expects(:value).with(:operatingsystemmajrelease).at_least_once.returns("13") - described_class.default?.should be_true + described_class.default?.should be_truthy end it "should not be the default provider on sles11" do Facter.expects(:value).with(:osfamily).at_least_once.returns(:suse) Facter.expects(:value).with(:operatingsystemmajrelease).at_least_once.returns("11") - described_class.default?.should_not be_true + described_class.default?.should_not be_truthy end [:enabled?, :enable, :disable, :start, :stop, :status, :restart].each do |method| it "should have a #{method} method" do provider.should respond_to(method) end end describe ".instances" do it "should have an instances method" do described_class.should respond_to :instances end it "should return only services" do described_class.expects(:systemctl).with('list-unit-files', '--type', 'service', '--full', '--all', '--no-pager').returns File.read(my_fixture('list_unit_files_services')) described_class.instances.map(&:name).should =~ %w{ arp-ethers.service auditd.service autovt@.service avahi-daemon.service blk-availability.service } 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.service', :start => '/bin/foo')) provider.expects(:execute).with(['/bin/foo'], :failonfail => true, :override_locale => false, :squelch => false, :combine => true) provider.start end it "should start the service with systemctl start otherwise" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd.service')) provider.expects(:execute).with(['/bin/systemctl','start','sshd.service'], :failonfail => true, :override_locale => false, :squelch => false, :combine => 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.service', :stop => '/bin/foo')) provider.expects(:execute).with(['/bin/foo'], :failonfail => true, :override_locale => false, :squelch => false, :combine => true) provider.stop end it "should stop the service with systemctl stop otherwise" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd.service')) provider.expects(:execute).with(['/bin/systemctl','stop','sshd.service'], :failonfail => true, :override_locale => false, :squelch => false, :combine => true) provider.stop end end describe "#enabled?" do it "should return :true if the service is enabled" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd.service')) provider.expects(:systemctl).with('is-enabled', 'sshd.service').returns 'enabled' provider.enabled?.should == :true end it "should return :false if the service is disabled" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd.service')) provider.expects(:systemctl).with('is-enabled', 'sshd.service').raises Puppet::ExecutionFailure, "Execution of '/bin/systemctl is-enabled sshd.service' returned 1: disabled" provider.enabled?.should == :false end end describe "#enable" do it "should run systemctl enable to enable a service" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd.service')) provider.expects(:systemctl).with('enable', 'sshd.service') provider.enable end end describe "#disable" do it "should run systemctl disable to disable a service" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd.service')) provider.expects(:systemctl).with(:disable, 'sshd.service') provider.disable end end # Note: systemd provider does not care about hasstatus or a custom status # command. I just assume that it does not make sense for systemd. describe "#status" do it "should return running if active" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd.service')) provider.expects(:systemctl).with('is-active', 'sshd.service').returns 'active' provider.status.should == :running end it "should return stopped if inactive" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd.service')) provider.expects(:systemctl).with('is-active', 'sshd.service').raises Puppet::ExecutionFailure, "Execution of '/bin/systemctl is-active sshd.service' returned 3: inactive" provider.status.should == :stopped end end # Note: systemd provider does not care about hasrestart. I just assume it # does not make sense for systemd 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(['/bin/systemctl','restart','sshd.service'], :failonfail => true, :override_locale => false, :squelch => false, :combine => true).never provider.expects(:execute).with(['/bin/foo'], :failonfail => true, :override_locale => false, :squelch => false, :combine => true) provider.restart end it "should restart the service with systemctl restart" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd.service')) provider.expects(:execute).with(['/bin/systemctl','restart','sshd.service'], :failonfail => true, :override_locale => false, :squelch => false, :combine => true) provider.restart end end it "(#16451) has command systemctl without being fully qualified" do described_class.instance_variable_get(:@commands). should include(:systemctl => 'systemctl') end end diff --git a/spec/unit/provider/service/upstart_spec.rb b/spec/unit/provider/service/upstart_spec.rb index ba55cb846..e5a0f98f2 100755 --- a/spec/unit/provider/service/upstart_spec.rb +++ b/spec/unit/provider/service/upstart_spec.rb @@ -1,590 +1,590 @@ #! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:service).provider(:upstart) do let(:manual) { "\nmanual" } let(:start_on_default_runlevels) { "\nstart on runlevel [2,3,4,5]" } let(:provider_class) { Puppet::Type.type(:service).provider(:upstart) } def given_contents_of(file, content) File.open(file, 'w') do |file| file.write(content) end end def then_contents_of(file) File.open(file).read end def lists_processes_as(output) Puppet::Util::Execution.stubs(:execpipe).with("/sbin/initctl list").yields(output) provider_class.stubs(:which).with("/sbin/initctl").returns("/sbin/initctl") end it "should be the default provider on Ubuntu" do Facter.expects(:value).with(:operatingsystem).returns("Ubuntu") - described_class.default?.should be_true + described_class.default?.should be_truthy end describe "excluding services" do it "ignores tty and serial on Redhat systems" do Facter.stubs(:value).with(:osfamily).returns('RedHat') expect(described_class.excludes).to include 'serial' expect(described_class.excludes).to include 'tty' end end describe "#instances" do it "should be able to find all instances" do lists_processes_as("rc stop/waiting\nssh start/running, process 712") provider_class.instances.map {|provider| provider.name}.should =~ ["rc","ssh"] end it "should attach the interface name for network interfaces" do lists_processes_as("network-interface (eth0)") provider_class.instances.first.name.should == "network-interface INTERFACE=eth0" end it "should attach the job name for network interface security" do processes = "network-interface-security (network-interface/eth0)" provider_class.stubs(:execpipe).yields(processes) provider_class.instances.first.name.should == "network-interface-security JOB=network-interface/eth0" end it "should not find excluded services" do processes = "wait-for-state stop/waiting" processes += "\nportmap-wait start/running" processes += "\nidmapd-mounting stop/waiting" processes += "\nstartpar-bridge start/running" processes += "\ncryptdisks-udev stop/waiting" processes += "\nstatd-mounting stop/waiting" processes += "\ngssd-mounting stop/waiting" provider_class.stubs(:execpipe).yields(processes) provider_class.instances.should be_empty end end describe "#search" do it "searches through paths to find a matching conf file" do File.stubs(:directory?).returns(true) Puppet::FileSystem.stubs(:exist?).returns(false) Puppet::FileSystem.expects(:exist?).with("/etc/init/foo-bar.conf").returns(true) resource = Puppet::Type.type(:service).new(:name => "foo-bar", :provider => :upstart) provider = provider_class.new(resource) provider.initscript.should == "/etc/init/foo-bar.conf" end it "searches for just the name of a compound named service" do File.stubs(:directory?).returns(true) Puppet::FileSystem.stubs(:exist?).returns(false) Puppet::FileSystem.expects(:exist?).with("/etc/init/network-interface.conf").returns(true) resource = Puppet::Type.type(:service).new(:name => "network-interface INTERFACE=lo", :provider => :upstart) provider = provider_class.new(resource) provider.initscript.should == "/etc/init/network-interface.conf" end end describe "#status" do it "should use the default status command if none is specified" do resource = Puppet::Type.type(:service).new(:name => "foo", :provider => :upstart) provider = provider_class.new(resource) provider.stubs(:is_upstart?).returns(true) provider.expects(:status_exec).with(["foo"]).returns("foo start/running, process 1000") Process::Status.any_instance.stubs(:exitstatus).returns(0) provider.status.should == :running end describe "when a special status command is specifed" do it "should use the provided status command" do resource = Puppet::Type.type(:service).new(:name => 'foo', :provider => :upstart, :status => '/bin/foo') provider = provider_class.new(resource) provider.stubs(:is_upstart?).returns(true) provider.expects(:status_exec).with(['foo']).never provider.expects(:execute).with(['/bin/foo'], :failonfail => false, :override_locale => false, :squelch => false, :combine => true) Process::Status.any_instance.stubs(:exitstatus).returns(0) provider.status end it "should return :stopped when the provided status command return non-zero" do resource = Puppet::Type.type(:service).new(:name => 'foo', :provider => :upstart, :status => '/bin/foo') provider = provider_class.new(resource) provider.stubs(:is_upstart?).returns(true) provider.expects(:status_exec).with(['foo']).never provider.expects(:execute).with(['/bin/foo'], :failonfail => false, :override_locale => false, :squelch => false, :combine => true) $CHILD_STATUS.stubs(:exitstatus).returns 1 provider.status.should == :stopped end it "should return :running when the provided status command return zero" do resource = Puppet::Type.type(:service).new(:name => 'foo', :provider => :upstart, :status => '/bin/foo') provider = provider_class.new(resource) provider.stubs(:is_upstart?).returns(true) provider.expects(:status_exec).with(['foo']).never provider.expects(:execute).with(['/bin/foo'], :failonfail => false, :override_locale => false, :squelch => false, :combine => true) $CHILD_STATUS.stubs(:exitstatus).returns 0 provider.status.should == :running end end describe "when :hasstatus is set to false" do it "should return :stopped if the pid can not be found" do resource = Puppet::Type.type(:service).new(:name => 'foo', :hasstatus => false, :provider => :upstart) provider = provider_class.new(resource) provider.stubs(:is_upstart?).returns(true) provider.expects(:status_exec).with(['foo']).never provider.expects(:getpid).returns nil provider.status.should == :stopped end it "should return :running if the pid can be found" do resource = Puppet::Type.type(:service).new(:name => 'foo', :hasstatus => false, :provider => :upstart) provider = provider_class.new(resource) provider.stubs(:is_upstart?).returns(true) provider.expects(:status_exec).with(['foo']).never provider.expects(:getpid).returns 2706 provider.status.should == :running end end it "should properly handle services with 'start' in their name" do resource = Puppet::Type.type(:service).new(:name => "foostartbar", :provider => :upstart) provider = provider_class.new(resource) provider.stubs(:is_upstart?).returns(true) provider.expects(:status_exec).with(["foostartbar"]).returns("foostartbar stop/waiting") Process::Status.any_instance.stubs(:exitstatus).returns(0) provider.status.should == :stopped end end describe "inheritance" do let :resource do resource = Puppet::Type.type(:service).new(:name => "foo", :provider => :upstart) end let :provider do provider = provider_class.new(resource) end describe "when upstart job" do before(:each) do provider.stubs(:is_upstart?).returns(true) end ["start", "stop"].each do |command| it "should return the #{command}cmd of its parent provider" do provider.send("#{command}cmd".to_sym).should == [provider.command(command.to_sym), resource.name] end end it "should return nil for the statuscmd" do provider.statuscmd.should be_nil end end end describe "should be enableable" do let :resource do Puppet::Type.type(:service).new(:name => "foo", :provider => :upstart) end let :provider do provider_class.new(resource) end let :init_script do PuppetSpec::Files.tmpfile("foo.conf") end let :over_script do PuppetSpec::Files.tmpfile("foo.override") end let :disabled_content do "\t # \t start on\nother file stuff" end let :multiline_disabled do "# \t start on other file stuff (\n" + "# more stuff ( # )))))inline comment\n" + "# finishing up )\n" + "# and done )\n" + "this line shouldn't be touched\n" end let :multiline_disabled_bad do "# \t start on other file stuff (\n" + "# more stuff ( # )))))inline comment\n" + "# finishing up )\n" + "# and done )\n" + "# this is a comment i want to be a comment\n" + "this line shouldn't be touched\n" end let :multiline_enabled_bad do " \t start on other file stuff (\n" + " more stuff ( # )))))inline comment\n" + " finishing up )\n" + " and done )\n" + "# this is a comment i want to be a comment\n" + "this line shouldn't be touched\n" end let :multiline_enabled do " \t start on other file stuff (\n" + " more stuff ( # )))))inline comment\n" + " finishing up )\n" + " and done )\n" + "this line shouldn't be touched\n" end let :multiline_enabled_standalone do " \t start on other file stuff (\n" + " more stuff ( # )))))inline comment\n" + " finishing up )\n" + " and done )\n" end let :enabled_content do "\t \t start on\nother file stuff" end let :content do "just some text" end describe "Upstart version < 0.6.7" do before(:each) do provider.stubs(:is_upstart?).returns(true) provider.stubs(:upstart_version).returns("0.6.5") provider.stubs(:search).returns(init_script) end [:enabled?,:enable,:disable].each do |enableable| it "should respond to #{enableable}" do provider.should respond_to(enableable) end end describe "when enabling" do it "should open and uncomment the '#start on' line" do given_contents_of(init_script, disabled_content) provider.enable then_contents_of(init_script).should == enabled_content end it "should add a 'start on' line if none exists" do given_contents_of(init_script, "this is a file") provider.enable then_contents_of(init_script).should == "this is a file" + start_on_default_runlevels end it "should handle multiline 'start on' stanzas" do given_contents_of(init_script, multiline_disabled) provider.enable then_contents_of(init_script).should == multiline_enabled end it "should leave not 'start on' comments alone" do given_contents_of(init_script, multiline_disabled_bad) provider.enable then_contents_of(init_script).should == multiline_enabled_bad end end describe "when disabling" do it "should open and comment the 'start on' line" do given_contents_of(init_script, enabled_content) provider.disable then_contents_of(init_script).should == "#" + enabled_content end it "should handle multiline 'start on' stanzas" do given_contents_of(init_script, multiline_enabled) provider.disable then_contents_of(init_script).should == multiline_disabled end end describe "when checking whether it is enabled" do it "should consider 'start on ...' to be enabled" do given_contents_of(init_script, enabled_content) provider.enabled?.should == :true end it "should consider '#start on ...' to be disabled" do given_contents_of(init_script, disabled_content) provider.enabled?.should == :false end it "should consider no start on line to be disabled" do given_contents_of(init_script, content) provider.enabled?.should == :false end end end describe "Upstart version < 0.9.0" do before(:each) do provider.stubs(:is_upstart?).returns(true) provider.stubs(:upstart_version).returns("0.7.0") provider.stubs(:search).returns(init_script) end [:enabled?,:enable,:disable].each do |enableable| it "should respond to #{enableable}" do provider.should respond_to(enableable) end end describe "when enabling" do it "should open and uncomment the '#start on' line" do given_contents_of(init_script, disabled_content) provider.enable then_contents_of(init_script).should == enabled_content end it "should add a 'start on' line if none exists" do given_contents_of(init_script, "this is a file") provider.enable then_contents_of(init_script).should == "this is a file" + start_on_default_runlevels end it "should handle multiline 'start on' stanzas" do given_contents_of(init_script, multiline_disabled) provider.enable then_contents_of(init_script).should == multiline_enabled end it "should remove manual stanzas" do given_contents_of(init_script, multiline_enabled + manual) provider.enable then_contents_of(init_script).should == multiline_enabled end it "should leave not 'start on' comments alone" do given_contents_of(init_script, multiline_disabled_bad) provider.enable then_contents_of(init_script).should == multiline_enabled_bad end end describe "when disabling" do it "should add a manual stanza" do given_contents_of(init_script, enabled_content) provider.disable then_contents_of(init_script).should == enabled_content + manual end it "should remove manual stanzas before adding new ones" do given_contents_of(init_script, multiline_enabled + manual + "\n" + multiline_enabled) provider.disable then_contents_of(init_script).should == multiline_enabled + "\n" + multiline_enabled + manual end it "should handle multiline 'start on' stanzas" do given_contents_of(init_script, multiline_enabled) provider.disable then_contents_of(init_script).should == multiline_enabled + manual end end describe "when checking whether it is enabled" do describe "with no manual stanza" do it "should consider 'start on ...' to be enabled" do given_contents_of(init_script, enabled_content) provider.enabled?.should == :true end it "should consider '#start on ...' to be disabled" do given_contents_of(init_script, disabled_content) provider.enabled?.should == :false end it "should consider no start on line to be disabled" do given_contents_of(init_script, content) provider.enabled?.should == :false end end describe "with manual stanza" do it "should consider 'start on ...' to be disabled if there is a trailing manual stanza" do given_contents_of(init_script, enabled_content + manual + "\nother stuff") provider.enabled?.should == :false end it "should consider two start on lines with a manual in the middle to be enabled" do given_contents_of(init_script, enabled_content + manual + "\n" + enabled_content) provider.enabled?.should == :true end end end end describe "Upstart version > 0.9.0" do before(:each) do provider.stubs(:is_upstart?).returns(true) provider.stubs(:upstart_version).returns("0.9.5") provider.stubs(:search).returns(init_script) provider.stubs(:overscript).returns(over_script) end [:enabled?,:enable,:disable].each do |enableable| it "should respond to #{enableable}" do provider.should respond_to(enableable) end end describe "when enabling" do it "should add a 'start on' line if none exists" do given_contents_of(init_script, "this is a file") provider.enable then_contents_of(init_script).should == "this is a file" then_contents_of(over_script).should == start_on_default_runlevels end it "should handle multiline 'start on' stanzas" do given_contents_of(init_script, multiline_disabled) provider.enable then_contents_of(init_script).should == multiline_disabled then_contents_of(over_script).should == start_on_default_runlevels end it "should remove any manual stanzas from the override file" do given_contents_of(over_script, manual) given_contents_of(init_script, enabled_content) provider.enable then_contents_of(init_script).should == enabled_content then_contents_of(over_script).should == "" end it "should copy existing start on from conf file if conf file is disabled" do given_contents_of(init_script, multiline_enabled_standalone + manual) provider.enable then_contents_of(init_script).should == multiline_enabled_standalone + manual then_contents_of(over_script).should == multiline_enabled_standalone end it "should leave not 'start on' comments alone" do given_contents_of(init_script, multiline_disabled_bad) given_contents_of(over_script, "") provider.enable then_contents_of(init_script).should == multiline_disabled_bad then_contents_of(over_script).should == start_on_default_runlevels end end describe "when disabling" do it "should add a manual stanza to the override file" do given_contents_of(init_script, enabled_content) provider.disable then_contents_of(init_script).should == enabled_content then_contents_of(over_script).should == manual end it "should handle multiline 'start on' stanzas" do given_contents_of(init_script, multiline_enabled) provider.disable then_contents_of(init_script).should == multiline_enabled then_contents_of(over_script).should == manual end end describe "when checking whether it is enabled" do describe "with no override file" do it "should consider 'start on ...' to be enabled" do given_contents_of(init_script, enabled_content) provider.enabled?.should == :true end it "should consider '#start on ...' to be disabled" do given_contents_of(init_script, disabled_content) provider.enabled?.should == :false end it "should consider no start on line to be disabled" do given_contents_of(init_script, content) provider.enabled?.should == :false end end describe "with override file" do it "should consider 'start on ...' to be disabled if there is manual in override file" do given_contents_of(init_script, enabled_content) given_contents_of(over_script, manual + "\nother stuff") provider.enabled?.should == :false end it "should consider '#start on ...' to be enabled if there is a start on in the override file" do given_contents_of(init_script, disabled_content) given_contents_of(over_script, "start on stuff") provider.enabled?.should == :true end end end end end end diff --git a/spec/unit/provider/user/aix_spec.rb b/spec/unit/provider/user/aix_spec.rb index b9edaff49..0537cff2f 100644 --- a/spec/unit/provider/user/aix_spec.rb +++ b/spec/unit/provider/user/aix_spec.rb @@ -1,162 +1,162 @@ require 'spec_helper' provider_class = Puppet::Type.type(:user).provider(:aix) describe provider_class do let(:lsuser_all_example) do <<-OUTPUT root id=0 pgrp=system groups=system,bin,sys,security,cron,audit,lp home=/root shell=/usr/bin/bash auditclasses=general login=true su=true rlogin=true daemon=true admin=true sugroups=ALL admgroups=lolt,allstaff tpath=nosak ttys=ALL expires=0 auth1=SYSTEM auth2=NONE umask=22 registry=files SYSTEM=compat logintimes= loginretries=0 pwdwarntime=0 account_locked=false minage=0 maxage=0 maxexpired=-1 minalpha=0 minother=0 mindiff=0 maxrepeats=8 minlen=0 histexpire=0 histsize=0 pwdchecks= dictionlist= default_roles= fsize=2097151 cpu=-1 data=262144 stack=65536 core=2097151 rss=65536 nofiles=2000 time_last_login=1358465855 time_last_unsuccessful_login=1358378454 tty_last_login=ssh tty_last_unsuccessful_login=ssh host_last_login=rpm-builder.puppetlabs.lan host_last_unsuccessful_login=192.168.100.78 unsuccessful_login_count=0 roles= guest id=100 pgrp=usr groups=usr home=/home/guest login=true su=true rlogin=true daemon=true admin=false sugroups=ALL admgroups= tpath=nosak ttys=ALL expires=0 auth1=SYSTEM auth2=NONE umask=22 registry=files SYSTEM=compat logintimes= loginretries=0 pwdwarntime=0 account_locked=false minage=0 maxage=0 maxexpired=-1 minalpha=0 minother=0 mindiff=0 maxrepeats=8 minlen=0 histexpire=0 histsize=0 pwdchecks= dictionlist= default_roles= fsize=2097151 cpu=-1 data=262144 stack=65536 core=2097151 rss=65536 nofiles=2000 roles= OUTPUT end let(:lsgroup_all_example) do <<-OUTPUT root id=0 pgrp=system groups=system,bin,sys,security,cron,audit,lp home=/root shell=/usr/bin/bash guest id=100 pgrp=usr groups=usr home=/home/guest OUTPUT end before do @resource = stub('resource') @provider = provider_class.new(@resource) end it "should be able to return a group name based on a group ID" do @provider.stubs(:lsgroupscmd) @provider.stubs(:execute).returns(lsgroup_all_example) @provider.groupname_by_id(100).should == 'guest' end it "should be able to list all users" do provider_class.stubs(:command) provider_class.stubs(:execute).returns(lsuser_all_example) provider_class.list_all.should == ['root', 'guest'] end describe "#managed_attribute_keys" do let(:existing_attributes) do { :account_locked => 'false', :admin => 'false', :login => 'true', 'su' => 'true' } end before(:each) do original_parameters = { :attributes => attribute_array } @resource.stubs(:original_parameters).returns(original_parameters) end describe "invoked via manifest" do let(:attribute_array) { ["rlogin=false", "login =true"] } it "should return only the keys of the attribute key=value pair from manifest" do keys = @provider.managed_attribute_keys(existing_attributes) keys.should be_include(:rlogin) keys.should be_include(:login) keys.should_not be_include(:su) end it "should strip spaces from symbols" do keys = @provider.managed_attribute_keys(existing_attributes) keys.should be_include(:login) keys.should_not be_include(:"login ") end it "should have the same count as that from the manifest" do keys = @provider.managed_attribute_keys(existing_attributes) keys.size.should == attribute_array.size end it "should convert the keys to symbols" do keys = @provider.managed_attribute_keys(existing_attributes) all_symbols = keys.all? {|k| k.is_a? Symbol} - all_symbols.should be_true + all_symbols.should be_truthy end end describe "invoked via RAL" do let(:attribute_array) { nil } it "should return the keys in supplied hash" do keys = @provider.managed_attribute_keys(existing_attributes) keys.should_not be_include(:rlogin) keys.should be_include(:login) keys.should be_include(:su) end it "should convert the keys to symbols" do keys = @provider.managed_attribute_keys(existing_attributes) all_symbols = keys.all? {|k| k.is_a? Symbol} - all_symbols.should be_true + all_symbols.should be_truthy end end end describe "#should_include?" do it "should exclude keys translated into something else" do managed_keys = [:rlogin] @provider.class.attribute_mapping_from.stubs(:include?).with(:rlogin).returns(true) @provider.class.stubs(:attribute_ignore).returns([]) - @provider.should_include?(:rlogin, managed_keys).should be_false + @provider.should_include?(:rlogin, managed_keys).should be_falsey end it "should exclude keys explicitly ignored" do managed_keys = [:rlogin] @provider.class.attribute_mapping_from.stubs(:include?).with(:rlogin).returns(false) @provider.class.stubs(:attribute_ignore).returns([:rlogin]) - @provider.should_include?(:rlogin, managed_keys).should be_false + @provider.should_include?(:rlogin, managed_keys).should be_falsey end it "should exclude keys not specified in manifest" do managed_keys = [:su] @provider.class.attribute_mapping_from.stubs(:include?).with(:rlogin).returns(false) @provider.class.stubs(:attribute_ignore).returns([]) - @provider.should_include?(:rlogin, managed_keys).should be_false + @provider.should_include?(:rlogin, managed_keys).should be_falsey end it "should include keys specified in manifest if not translated or ignored" do managed_keys = [:rlogin] @provider.class.attribute_mapping_from.stubs(:include?).with(:rlogin).returns(false) @provider.class.stubs(:attribute_ignore).returns([]) - @provider.should_include?(:rlogin, managed_keys).should be_true + @provider.should_include?(:rlogin, managed_keys).should be_truthy end end describe "when handling passwords" do let(:passwd_without_spaces) do # from http://pic.dhe.ibm.com/infocenter/aix/v7r1/index.jsp?topic=%2Fcom.ibm.aix.files%2Fdoc%2Faixfiles%2Fpasswd_security.htm <<-OUTPUT smith: password = MGURSj.F056Dj lastupdate = 623078865 flags = ADMIN,NOCHECK OUTPUT end let(:passwd_with_spaces) do # add trailing space to the password passwd_without_spaces.gsub(/password = (.*)/, 'password = \1 ') end it "should be able to read the hashed password" do @provider.stubs(:open_security_passwd).returns(StringIO.new(passwd_without_spaces)) @resource.stubs(:[]).returns('smith') @provider.password.should == 'MGURSj.F056Dj' end it "should be able to read the hashed password, even with trailing spaces" do @provider.stubs(:open_security_passwd).returns(StringIO.new(passwd_with_spaces)) @resource.stubs(:[]).returns('smith') @provider.password.should == 'MGURSj.F056Dj' end end end diff --git a/spec/unit/provider_spec.rb b/spec/unit/provider_spec.rb index 3c55431fc..be6bfb6eb 100755 --- a/spec/unit/provider_spec.rb +++ b/spec/unit/provider_spec.rb @@ -1,701 +1,701 @@ #! /usr/bin/env ruby require 'spec_helper' def existing_command Puppet.features.microsoft_windows? ? "cmd" : "echo" end describe Puppet::Provider do before :each do Puppet::Type.newtype(:test) do newparam(:name) { isnamevar } end end after :each do Puppet::Type.rmtype(:test) end let :type do Puppet::Type.type(:test) end let :provider do type.provide(:default) {} end subject { provider } describe "has command" do it "installs a method to run the command specified by the path" do echo_command = expect_command_executed(:echo, "/bin/echo", "an argument") allow_creation_of(echo_command) provider = provider_of do has_command(:echo, "/bin/echo") end provider.echo("an argument") end it "installs a command that is run with a given environment" do echo_command = expect_command_executed(:echo, "/bin/echo", "an argument") allow_creation_of(echo_command, { :EV => "value", :OTHER => "different" }) provider = provider_of do has_command(:echo, "/bin/echo") do environment :EV => "value", :OTHER => "different" end end provider.echo("an argument") end it "is required by default" do provider = provider_of do has_command(:does_not_exist, "/does/not/exist") end provider.should_not be_suitable end it "is required by default" do provider = provider_of do has_command(:does_exist, File.expand_path("/exists/somewhere")) end file_exists_and_is_executable(File.expand_path("/exists/somewhere")) provider.should be_suitable end it "can be specified as optional" do provider = provider_of do has_command(:does_not_exist, "/does/not/exist") do is_optional end end provider.should be_suitable end end describe "has required commands" do it "installs methods to run executables by path" do echo_command = expect_command_executed(:echo, "/bin/echo", "an argument") ls_command = expect_command_executed(:ls, "/bin/ls") allow_creation_of(echo_command) allow_creation_of(ls_command) provider = provider_of do commands :echo => "/bin/echo", :ls => "/bin/ls" end provider.echo("an argument") provider.ls end it "allows the provider to be suitable if the executable is present" do provider = provider_of do commands :always_exists => File.expand_path("/this/command/exists") end file_exists_and_is_executable(File.expand_path("/this/command/exists")) provider.should be_suitable end it "does not allow the provider to be suitable if the executable is not present" do provider = provider_of do commands :does_not_exist => "/this/command/does/not/exist" end provider.should_not be_suitable end end describe "has optional commands" do it "installs methods to run executables" do echo_command = expect_command_executed(:echo, "/bin/echo", "an argument") ls_command = expect_command_executed(:ls, "/bin/ls") allow_creation_of(echo_command) allow_creation_of(ls_command) provider = provider_of do optional_commands :echo => "/bin/echo", :ls => "/bin/ls" end provider.echo("an argument") provider.ls end it "allows the provider to be suitable even if the executable is not present" do provider = provider_of do optional_commands :does_not_exist => "/this/command/does/not/exist" end provider.should be_suitable end end it "should have a specifity class method" do Puppet::Provider.should respond_to(:specificity) end it "should be Comparable" do res = Puppet::Type.type(:notify).new(:name => "res") # Normally I wouldn't like the stubs, but the only way to name a class # otherwise is to assign it to a constant, and that hurts more here in # testing world. --daniel 2012-01-29 a = Class.new(Puppet::Provider).new(res) a.class.stubs(:name).returns "Puppet::Provider::Notify::A" b = Class.new(Puppet::Provider).new(res) b.class.stubs(:name).returns "Puppet::Provider::Notify::B" c = Class.new(Puppet::Provider).new(res) c.class.stubs(:name).returns "Puppet::Provider::Notify::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.should be < b a.should be < c b.should be > a b.should be < c c.should be > a c.should be > b [a, b, c].each {|x| a.should be <= x } [a, b, c].each {|x| c.should be >= x } b.should be_between(a, c) end context "when creating instances" do context "with a resource" do let :resource do type.new(:name => "fred") end subject { provider.new(resource) } it "should set the resource correctly" do subject.resource.must equal resource end it "should set the name from the resource" do subject.name.should == resource.name end end context "with a hash" do subject { provider.new(:name => "fred") } it "should set the name" do subject.name.should == "fred" end it "should not have a resource" do subject.resource.should be_nil end end context "with no arguments" do subject { provider.new } it "should raise an internal error if asked for the name" do expect { subject.name }.to raise_error Puppet::DevError end it "should not have a resource" do subject.resource.should be_nil end end end context "when confining" do it "should be suitable by default" do subject.should be_suitable end it "should not be default by default" do subject.should_not be_default end { { :true => true } => true, { :true => false } => false, { :false => false } => true, { :false => true } => false, { :operatingsystem => Facter.value(:operatingsystem) } => true, { :operatingsystem => :yayness } => false, { :nothing => :yayness } => false, { :exists => Puppet::Util.which(existing_command) } => true, { :exists => "/this/file/does/not/exist" } => false, { :true => true, :exists => Puppet::Util.which(existing_command) } => true, { :true => true, :exists => "/this/file/does/not/exist" } => false, { :operatingsystem => Facter.value(:operatingsystem), :exists => Puppet::Util.which(existing_command) } => true, { :operatingsystem => :yayness, :exists => Puppet::Util.which(existing_command) } => false, { :operatingsystem => Facter.value(:operatingsystem), :exists => "/this/file/does/not/exist" } => false, { :operatingsystem => :yayness, :exists => "/this/file/does/not/exist" } => false, }.each do |confines, result| it "should confine #{confines.inspect} to #{result}" do confines.each {|test, value| subject.confine test => value } subject.send(result ? :should : :should_not, be_suitable) end end it "should not override a confine even if a second has the same type" do subject.confine :true => false subject.should_not be_suitable subject.confine :true => true subject.should_not be_suitable end it "should not be suitable if any confine fails" do subject.confine :true => false subject.should_not be_suitable 10.times do subject.confine :true => true subject.should_not be_suitable end end end context "default providers" do let :os do Facter.value(:operatingsystem) end it { should respond_to :specificity } it "should find the default provider" do type.provide(:nondefault) {} subject.defaultfor :operatingsystem => os subject.name.should == type.defaultprovider.name end describe "when there are multiple defaultfor's of equal specificity" do before :each do subject.defaultfor :operatingsystem => :os1 subject.defaultfor :operatingsystem => :os2 end let(:alternate) { type.provide(:alternate) {} } it "should be default for the first defaultfor" do Facter.expects(:value).with(:operatingsystem).at_least_once.returns :os1 provider.should be_default alternate.should_not be_default end it "should be default for the last defaultfor" do Facter.expects(:value).with(:operatingsystem).at_least_once.returns :os2 provider.should be_default alternate.should_not be_default end end describe "when there are multiple defaultfor's with different specificity" do before :each do subject.defaultfor :operatingsystem => :os1 subject.defaultfor :operatingsystem => :os2, :operatingsystemmajrelease => "42" end let(:alternate) { type.provide(:alternate) {} } it "should be default for a more specific, but matching, defaultfor" do Facter.expects(:value).with(:operatingsystem).at_least_once.returns :os2 Facter.expects(:value).with(:operatingsystemmajrelease).at_least_once.returns "42" provider.should be_default alternate.should_not be_default end it "should be default for a less specific, but matching, defaultfor" do Facter.expects(:value).with(:operatingsystem).at_least_once.returns :os1 provider.should be_default alternate.should_not be_default end end it "should consider any true value enough to be default" do alternate = type.provide(:alternate) {} subject.defaultfor :operatingsystem => [:one, :two, :three, os] subject.name.should == type.defaultprovider.name subject.should be_default alternate.should_not be_default end it "should not be default if the defaultfor doesn't match" do subject.should_not be_default subject.defaultfor :operatingsystem => :one subject.should_not be_default end it "should consider two defaults to be higher specificity than one default" do Facter.expects(:value).with(:osfamily).at_least_once.returns "solaris" Facter.expects(:value).with(:operatingsystemrelease).at_least_once.returns "5.10" one = type.provide(:one) do defaultfor :osfamily => "solaris" end two = type.provide(:two) do defaultfor :osfamily => "solaris", :operatingsystemrelease => "5.10" end two.specificity.should > one.specificity end it "should consider a subclass more specific than its parent class" do parent = type.provide(:parent) child = type.provide(:child, :parent => parent) child.specificity.should > parent.specificity end describe "using a :feature key" do before :each do Puppet.features.add(:yay) do true end Puppet.features.add(:boo) do false end end it "is default for an available feature" do one = type.provide(:one) do defaultfor :feature => :yay end one.should be_default end it "is not default for a missing feature" do two = type.provide(:two) do defaultfor :feature => :boo end two.should_not be_default end end end context "provider commands" do it "should raise for unknown commands" do expect { subject.command(:something) }.to raise_error(Puppet::DevError) end it "should handle command inheritance" do parent = type.provide("parent") child = type.provide("child", :parent => parent.name) command = Puppet::Util.which('sh') || Puppet::Util.which('cmd.exe') parent.commands :sh => command - Puppet::FileSystem.exist?(parent.command(:sh)).should be_true + Puppet::FileSystem.exist?(parent.command(:sh)).should be_truthy parent.command(:sh).should =~ /#{Regexp.escape(command)}$/ - Puppet::FileSystem.exist?(child.command(:sh)).should be_true + Puppet::FileSystem.exist?(child.command(:sh)).should be_truthy child.command(:sh).should =~ /#{Regexp.escape(command)}$/ end it "#1197: should find commands added in the same run" do subject.commands :testing => "puppet-bug-1197" subject.command(:testing).should be_nil subject.stubs(:which).with("puppet-bug-1197").returns("/puppet-bug-1197") subject.command(:testing).should == "/puppet-bug-1197" # Ideally, we would also test that `suitable?` returned the right thing # here, but it is impossible to get access to the methods that do that # without digging way down into the implementation. --daniel 2012-03-20 end context "with optional commands" do before :each do subject.optional_commands :cmd => "/no/such/binary/exists" end it { should be_suitable } it "should not be suitable if a mandatory command is also missing" do subject.commands :foo => "/no/such/binary/either" subject.should_not be_suitable end it "should define a wrapper for the command" do subject.should respond_to(:cmd) end it "should return nil if the command is requested" do subject.command(:cmd).should be_nil end it "should raise if the command is invoked" do expect { subject.cmd }.to raise_error(Puppet::Error, /Command cmd is missing/) end end end context "execution" do before :each do Puppet.expects(:deprecation_warning).never end it "delegates instance execute to Puppet::Util::Execution" do Puppet::Util::Execution.expects(:execute).with("a_command", { :option => "value" }) provider.new.send(:execute, "a_command", { :option => "value" }) end it "delegates class execute to Puppet::Util::Execution" do Puppet::Util::Execution.expects(:execute).with("a_command", { :option => "value" }) provider.send(:execute, "a_command", { :option => "value" }) end it "delegates instance execpipe to Puppet::Util::Execution" do block = Proc.new { } Puppet::Util::Execution.expects(:execpipe).with("a_command", true, block) provider.new.send(:execpipe, "a_command", true, block) end it "delegates class execpipe to Puppet::Util::Execution" do block = Proc.new { } Puppet::Util::Execution.expects(:execpipe).with("a_command", true, block) provider.send(:execpipe, "a_command", true, block) end it "delegates instance execfail to Puppet::Util::Execution" do Puppet::Util::Execution.expects(:execfail).with("a_command", "an exception to raise") provider.new.send(:execfail, "a_command", "an exception to raise") end it "delegates class execfail to Puppet::Util::Execution" do Puppet::Util::Execution.expects(:execfail).with("a_command", "an exception to raise") provider.send(:execfail, "a_command", "an exception to raise") end end context "mk_resource_methods" do before :each do type.newproperty(:prop) type.newparam(:param) provider.mk_resource_methods end let(:instance) { provider.new(nil) } it "defaults to :absent" do expect(instance.prop).to eq(:absent) expect(instance.param).to eq(:absent) end it "should update when set" do instance.prop = 'hello' instance.param = 'goodbye' expect(instance.prop).to eq('hello') expect(instance.param).to eq('goodbye') end it "treats nil the same as absent" do instance.prop = "value" instance.param = "value" instance.prop = nil instance.param = nil expect(instance.prop).to eq(:absent) expect(instance.param).to eq(:absent) end it "preserves false as false" do instance.prop = false instance.param = false expect(instance.prop).to eq(false) expect(instance.param).to eq(false) end end context "source" do it "should default to the provider name" do subject.source.should == :default end it "should default to the provider name for a child provider" do type.provide(:sub, :parent => subject.name).source.should == :sub end it "should override if requested" do provider = type.provide(:sub, :parent => subject.name, :source => subject.source) provider.source.should == subject.source end it "should override to anything you want" do expect { subject.source = :banana }.to change { subject.source }. from(:default).to(:banana) end end context "features" do before :each do type.feature :numeric, '', :methods => [:one, :two] type.feature :alpha, '', :methods => [:a, :b] type.feature :nomethods, '' end { :no => { :alpha => false, :numeric => false, :methods => [] }, :numeric => { :alpha => false, :numeric => true, :methods => [:one, :two] }, :alpha => { :alpha => true, :numeric => false, :methods => [:a, :b] }, :all => { :alpha => true, :numeric => true, :methods => [:a, :b, :one, :two] }, :alpha_and_partial => { :alpha => true, :numeric => false, :methods => [:a, :b, :one] }, :numeric_and_partial => { :alpha => false, :numeric => true, :methods => [:a, :one, :two] }, :all_partial => { :alpha => false, :numeric => false, :methods => [:a, :one] }, :other_and_none => { :alpha => false, :numeric => false, :methods => [:foo, :bar] }, :other_and_alpha => { :alpha => true, :numeric => false, :methods => [:foo, :bar, :a, :b] }, }.each do |name, setup| context "with #{name.to_s.gsub('_', ' ')} features" do let :provider do provider = type.provide(name) setup[:methods].map do |method| provider.send(:define_method, method) do true end end type.provider(name) end let :numeric? do setup[:numeric] ? :should : :should_not end let :alpha? do setup[:alpha] ? :should : :should_not end subject { provider } it { should respond_to(:has_features) } it { should respond_to(:has_feature) } context "provider class" do it { should respond_to(:nomethods?) } it { should_not be_nomethods } it { should respond_to(:numeric?) } it { subject.send(numeric?, be_numeric) } it { subject.send(numeric?, be_satisfies(:numeric)) } it { should respond_to(:alpha?) } it { subject.send(alpha?, be_alpha) } it { subject.send(alpha?, be_satisfies(:alpha)) } end context "provider instance" do subject { provider.new } it { should respond_to(:numeric?) } it { subject.send(numeric?, be_numeric) } it { subject.send(numeric?, be_satisfies(:numeric)) } it { should respond_to(:alpha?) } it { subject.send(alpha?, be_alpha) } it { subject.send(alpha?, be_satisfies(:alpha)) } end end end context "feature with no methods" do before :each do type.feature :undemanding, '' end it { should respond_to(:undemanding?) } context "when the feature is not declared" do it { should_not be_undemanding } it { should_not be_satisfies(:undemanding) } end context "when the feature is declared" do before :each do subject.has_feature :undemanding end it { should be_undemanding } it { should be_satisfies(:undemanding) } end end context "supports_parameter?" do before :each do type.newparam(:no_feature) type.newparam(:one_feature, :required_features => :alpha) type.newparam(:two_features, :required_features => [:alpha, :numeric]) end let :providers do { :zero => type.provide(:zero), :one => type.provide(:one) do has_features :alpha end, :two => type.provide(:two) do has_features :alpha, :numeric end } end { :zero => { :yes => [:no_feature], :no => [:one_feature, :two_features] }, :one => { :yes => [:no_feature, :one_feature], :no => [:two_features] }, :two => { :yes => [:no_feature, :one_feature, :two_features], :no => [] } }.each do |name, data| data[:yes].each do |param| it "should support #{param} with provider #{name}" do providers[name].should be_supports_parameter(param) end end data[:no].each do |param| it "should not support #{param} with provider #{name}" do providers[name].should_not be_supports_parameter(param) end end end end end def provider_of(options = {}, &block) type = Puppet::Type.newtype(:dummy) do provide(:dummy, options, &block) end type.provider(:dummy) end def expect_command_executed(name, path, *args) command = Puppet::Provider::Command.new(name, path, Puppet::Util, Puppet::Util::Execution) command.expects(:execute).with(*args) command end def allow_creation_of(command, environment = {}) Puppet::Provider::Command.stubs(:new).with(command.name, command.executable, Puppet::Util, Puppet::Util::Execution, { :failonfail => true, :combine => true, :custom_environment => environment }).returns(command) end def file_exists_and_is_executable(path) FileTest.expects(:file?).with(path).returns(true) FileTest.expects(:executable?).with(path).returns(true) end end diff --git a/spec/unit/reports_spec.rb b/spec/unit/reports_spec.rb index dfab2f690..ff15acc65 100755 --- a/spec/unit/reports_spec.rb +++ b/spec/unit/reports_spec.rb @@ -1,60 +1,60 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/reports' describe Puppet::Reports do it "should instance-load report types" do Puppet::Reports.instance_loader(:report).should be_instance_of(Puppet::Util::Autoload) end it "should have a method for registering report types" do Puppet::Reports.should respond_to(:register_report) end it "should have a method for retrieving report types by name" do Puppet::Reports.should respond_to(:report) end it "should provide a method for returning documentation for all reports" do Puppet::Reports.expects(:loaded_instances).with(:report).returns([:one, :two]) one = mock 'one', :doc => "onedoc" two = mock 'two', :doc => "twodoc" Puppet::Reports.expects(:report).with(:one).returns(one) Puppet::Reports.expects(:report).with(:two).returns(two) doc = Puppet::Reports.reportdocs - doc.include?("onedoc").should be_true - doc.include?("twodoc").should be_true + doc.include?("onedoc").should be_truthy + doc.include?("twodoc").should be_truthy end end describe Puppet::Reports, " when loading report types" do it "should use the instance loader to retrieve report types" do Puppet::Reports.expects(:loaded_instance).with(:report, :myreporttype) Puppet::Reports.report(:myreporttype) end end describe Puppet::Reports, " when registering report types" do it "should evaluate the supplied block as code for a module" do Puppet::Reports.expects(:genmodule).returns(Module.new) Puppet::Reports.register_report(:testing) { } end it "should extend the report type with the Puppet::Util::Docs module" do mod = stub 'module', :define_method => true Puppet::Reports.expects(:genmodule).with { |name, options, block| options[:extend] == Puppet::Util::Docs }.returns(mod) Puppet::Reports.register_report(:testing) { } end it "should define a :report_name method in the module that returns the name of the report" do mod = mock 'module' mod.expects(:define_method).with(:report_name) Puppet::Reports.expects(:genmodule).returns(mod) Puppet::Reports.register_report(:testing) { } end end diff --git a/spec/unit/resource/catalog_spec.rb b/spec/unit/resource/catalog_spec.rb index 5bc38c0bf..08539d337 100755 --- a/spec/unit/resource/catalog_spec.rb +++ b/spec/unit/resource/catalog_spec.rb @@ -1,866 +1,866 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/compiler' require 'matchers/json' describe Puppet::Resource::Catalog, "when compiling" do include JSONMatchers include PuppetSpec::Files before do @basepath = make_absolute("/somepath") # stub this to not try to create state.yaml Puppet::Util::Storage.stubs(:store) end # audit only resources are unmanaged # as are resources without properties with should values it "should write its managed resources' types, namevars" do catalog = Puppet::Resource::Catalog.new("host") resourcefile = tmpfile('resourcefile') Puppet[:resourcefile] = resourcefile res = Puppet::Type.type('file').new(:title => File.expand_path('/tmp/sam'), :ensure => 'present') res.file = 'site.pp' res.line = 21 res2 = Puppet::Type.type('exec').new(:title => 'bob', :command => "#{File.expand_path('/bin/rm')} -rf /") res2.file = File.expand_path('/modules/bob/manifests/bob.pp') res2.line = 42 res3 = Puppet::Type.type('file').new(:title => File.expand_path('/tmp/susan'), :audit => 'all') res3.file = 'site.pp' res3.line = 63 res4 = Puppet::Type.type('file').new(:title => File.expand_path('/tmp/lilly')) res4.file = 'site.pp' res4.line = 84 comp_res = Puppet::Type.type('component').new(:title => 'Class[Main]') catalog.add_resource(res, res2, res3, res4, comp_res) catalog.write_resource_file File.readlines(resourcefile).map(&:chomp).should =~ [ "file[#{File.expand_path('/tmp/sam')}]", "exec[#{File.expand_path('/bin/rm')} -rf /]" ] end it "should log an error if unable to write to the resource file" do catalog = Puppet::Resource::Catalog.new("host") Puppet[:resourcefile] = File.expand_path('/not/writable/file') catalog.add_resource(Puppet::Type.type('file').new(:title => File.expand_path('/tmp/foo'))) catalog.write_resource_file @logs.size.should == 1 @logs.first.message.should =~ /Could not create resource file/ @logs.first.level.should == :err end it "should be able to write its list of classes to the class file" do @catalog = Puppet::Resource::Catalog.new("host") @catalog.add_class "foo", "bar" Puppet[:classfile] = File.expand_path("/class/file") fh = mock 'filehandle' File.expects(:open).with(Puppet[:classfile], "w").yields fh fh.expects(:puts).with "foo\nbar" @catalog.write_class_file end it "should have a client_version attribute" do @catalog = Puppet::Resource::Catalog.new("host") @catalog.client_version = 5 @catalog.client_version.should == 5 end it "should have a server_version attribute" do @catalog = Puppet::Resource::Catalog.new("host") @catalog.server_version = 5 @catalog.server_version.should == 5 end describe "when compiling" do it "should accept tags" do config = Puppet::Resource::Catalog.new("mynode") config.tag("one") config.should be_tagged("one") end it "should accept multiple tags at once" do config = Puppet::Resource::Catalog.new("mynode") config.tag("one", "two") config.should be_tagged("one") config.should be_tagged("two") end it "should convert all tags to strings" do config = Puppet::Resource::Catalog.new("mynode") config.tag("one", :two) config.should be_tagged("one") config.should be_tagged("two") end it "should tag with both the qualified name and the split name" do config = Puppet::Resource::Catalog.new("mynode") config.tag("one::two") config.should be_tagged("one") config.should be_tagged("one::two") end it "should accept classes" do config = Puppet::Resource::Catalog.new("mynode") config.add_class("one") config.classes.should == %w{one} config.add_class("two", "three") config.classes.should == %w{one two three} end it "should tag itself with passed class names" do config = Puppet::Resource::Catalog.new("mynode") config.add_class("one") config.should be_tagged("one") end end describe "when converting to a RAL catalog" do before do @original = Puppet::Resource::Catalog.new("mynode") @original.tag(*%w{one two three}) @original.add_class *%w{four five six} @top = Puppet::Resource.new :class, 'top' @topobject = Puppet::Resource.new :file, @basepath+'/topobject' @middle = Puppet::Resource.new :class, 'middle' @middleobject = Puppet::Resource.new :file, @basepath+'/middleobject' @bottom = Puppet::Resource.new :class, 'bottom' @bottomobject = Puppet::Resource.new :file, @basepath+'/bottomobject' @resources = [@top, @topobject, @middle, @middleobject, @bottom, @bottomobject] @original.add_resource(*@resources) @original.add_edge(@top, @topobject) @original.add_edge(@top, @middle) @original.add_edge(@middle, @middleobject) @original.add_edge(@middle, @bottom) @original.add_edge(@bottom, @bottomobject) @catalog = @original.to_ral end it "should add all resources as RAL instances" do @resources.each do |resource| # Warning: a failure here will result in "global resource iteration is # deprecated" being raised, because the rspec rendering to get the # result tries to call `each` on the resource, and that raises. @catalog.resource(resource.ref).must be_a_kind_of(Puppet::Type) end end it "should copy the tag list to the new catalog" do @catalog.tags.sort.should == @original.tags.sort end it "should copy the class list to the new catalog" do @catalog.classes.should == @original.classes end it "should duplicate the original edges" do @original.edges.each do |edge| - @catalog.edge?(@catalog.resource(edge.source.ref), @catalog.resource(edge.target.ref)).should be_true + @catalog.edge?(@catalog.resource(edge.source.ref), @catalog.resource(edge.target.ref)).should be_truthy end end it "should set itself as the catalog for each converted resource" do @catalog.vertices.each { |v| v.catalog.object_id.should equal(@catalog.object_id) } end # This tests #931. it "should not lose track of resources whose names vary" do changer = Puppet::Resource.new :file, @basepath+'/test/', :parameters => {:ensure => :directory} config = Puppet::Resource::Catalog.new('test') config.add_resource(changer) config.add_resource(@top) config.add_edge(@top, changer) catalog = config.to_ral catalog.resource("File[#{@basepath}/test/]").must equal(catalog.resource("File[#{@basepath}/test]")) end after do # Remove all resource instances. @catalog.clear(true) end end describe "when filtering" do before :each do @original = Puppet::Resource::Catalog.new("mynode") @original.tag(*%w{one two three}) @original.add_class *%w{four five six} @r1 = stub_everything 'r1', :ref => "File[/a]" @r1.stubs(:respond_to?).with(:ref).returns(true) @r1.stubs(:copy_as_resource).returns(@r1) @r1.stubs(:is_a?).with(Puppet::Resource).returns(true) @r2 = stub_everything 'r2', :ref => "File[/b]" @r2.stubs(:respond_to?).with(:ref).returns(true) @r2.stubs(:copy_as_resource).returns(@r2) @r2.stubs(:is_a?).with(Puppet::Resource).returns(true) @resources = [@r1,@r2] @original.add_resource(@r1,@r2) end it "should transform the catalog to a resource catalog" do @original.expects(:to_catalog).with { |h,b| h == :to_resource } @original.filter end it "should scan each catalog resource in turn and apply filtering block" do @resources.each { |r| r.expects(:test?) } @original.filter do |r| r.test? end end it "should filter out resources which produce true when the filter block is evaluated" do @original.filter do |r| r == @r1 end.resource("File[/a]").should be_nil end it "should not consider edges against resources that were filtered out" do @original.add_edge(@r1,@r2) @original.filter do |r| r == @r1 end.edge?(@r1,@r2).should_not be end end describe "when functioning as a resource container" do before do @catalog = Puppet::Resource::Catalog.new("host") @one = Puppet::Type.type(:notify).new :name => "one" @two = Puppet::Type.type(:notify).new :name => "two" @dupe = Puppet::Type.type(:notify).new :name => "one" end it "should provide a method to add one or more resources" do @catalog.add_resource @one, @two @catalog.resource(@one.ref).must equal(@one) @catalog.resource(@two.ref).must equal(@two) end it "should add resources to the relationship graph if it exists" do relgraph = @catalog.relationship_graph @catalog.add_resource @one relgraph.should be_vertex(@one) end it "should set itself as the resource's catalog if it is not a relationship graph" do @one.expects(:catalog=).with(@catalog) @catalog.add_resource @one end it "should make all vertices available by resource reference" do @catalog.add_resource(@one) @catalog.resource(@one.ref).must equal(@one) @catalog.vertices.find { |r| r.ref == @one.ref }.must equal(@one) end it "tracks the container through edges" do @catalog.add_resource(@two) @catalog.add_resource(@one) @catalog.add_edge(@one, @two) @catalog.container_of(@two).must == @one end it "a resource without a container is contained in nil" do @catalog.add_resource(@one) @catalog.container_of(@one).must be_nil end it "should canonize how resources are referred to during retrieval when both type and title are provided" do @catalog.add_resource(@one) @catalog.resource("notify", "one").must equal(@one) end it "should canonize how resources are referred to during retrieval when just the title is provided" do @catalog.add_resource(@one) @catalog.resource("notify[one]", nil).must equal(@one) end describe 'with a duplicate resource' do def resource_at(type, name, file, line) resource = Puppet::Resource.new(type, name) resource.file = file resource.line = line Puppet::Type.type(type).new(resource) end let(:orig) { resource_at(:notify, 'duplicate-title', '/path/to/orig/file', 42) } let(:dupe) { resource_at(:notify, 'duplicate-title', '/path/to/dupe/file', 314) } it "should print the locations of the original duplicated resource" do @catalog.add_resource(orig) expect { @catalog.add_resource(dupe) }.to raise_error { |error| error.should be_a Puppet::Resource::Catalog::DuplicateResourceError error.message.should match %r[Duplicate declaration: Notify\[duplicate-title\] is already declared] error.message.should match %r[in file /path/to/orig/file:42] error.message.should match %r[cannot redeclare] error.message.should match %r[at /path/to/dupe/file:314] } end end it "should remove all resources when asked" do @catalog.add_resource @one @catalog.add_resource @two @one.expects :remove @two.expects :remove @catalog.clear(true) end it "should support a mechanism for finishing resources" do @one.expects :finish @two.expects :finish @catalog.add_resource @one @catalog.add_resource @two @catalog.finalize end it "should make default resources when finalizing" do @catalog.expects(:make_default_resources) @catalog.finalize end it "should add default resources to the catalog upon creation" do @catalog.make_default_resources @catalog.resource(:schedule, "daily").should_not be_nil end it "should optionally support an initialization block and should finalize after such blocks" do @one.expects :finish @two.expects :finish config = Puppet::Resource::Catalog.new("host") do |conf| conf.add_resource @one conf.add_resource @two end end it "should inform the resource that it is the resource's catalog" do @one.expects(:catalog=).with(@catalog) @catalog.add_resource @one end it "should be able to find resources by reference" do @catalog.add_resource @one @catalog.resource(@one.ref).must equal(@one) end it "should be able to find resources by reference or by type/title tuple" do @catalog.add_resource @one @catalog.resource("notify", "one").must equal(@one) end it "should have a mechanism for removing resources" do @catalog.add_resource(@one) @catalog.resource(@one.ref).must be - @catalog.vertex?(@one).must be_true + @catalog.vertex?(@one).must be_truthy @catalog.remove_resource(@one) @catalog.resource(@one.ref).must be_nil - @catalog.vertex?(@one).must be_false + @catalog.vertex?(@one).must be_falsey end it "should have a method for creating aliases for resources" do @catalog.add_resource @one @catalog.alias(@one, "other") @catalog.resource("notify", "other").must equal(@one) end it "should ignore conflicting aliases that point to the aliased resource" do @catalog.alias(@one, "other") lambda { @catalog.alias(@one, "other") }.should_not raise_error end it "should create aliases for isomorphic resources whose names do not match their titles" do resource = Puppet::Type::File.new(:title => "testing", :path => @basepath+"/something") @catalog.add_resource(resource) @catalog.resource(:file, @basepath+"/something").must equal(resource) end it "should not create aliases for non-isomorphic resources whose names do not match their titles" do resource = Puppet::Type.type(:exec).new(:title => "testing", :command => "echo", :path => %w{/bin /usr/bin /usr/local/bin}) @catalog.add_resource(resource) # Yay, I've already got a 'should' method @catalog.resource(:exec, "echo").object_id.should == nil.object_id end # This test is the same as the previous, but the behaviour should be explicit. it "should alias using the class name from the resource reference, not the resource class name" do @catalog.add_resource @one @catalog.alias(@one, "other") @catalog.resource("notify", "other").must equal(@one) end it "should fail to add an alias if the aliased name already exists" do @catalog.add_resource @one proc { @catalog.alias @two, "one" }.should raise_error(ArgumentError) end it "should not fail when a resource has duplicate aliases created" do @catalog.add_resource @one proc { @catalog.alias @one, "one" }.should_not raise_error end it "should not create aliases that point back to the resource" do @catalog.alias(@one, "one") @catalog.resource(:notify, "one").must be_nil end it "should be able to look resources up by their aliases" do @catalog.add_resource @one @catalog.alias @one, "two" @catalog.resource(:notify, "two").must equal(@one) end it "should remove resource aliases when the target resource is removed" do @catalog.add_resource @one @catalog.alias(@one, "other") @one.expects :remove @catalog.remove_resource(@one) @catalog.resource("notify", "other").must be_nil end it "should add an alias for the namevar when the title and name differ on isomorphic resource types" do resource = Puppet::Type.type(:file).new :path => @basepath+"/something", :title => "other", :content => "blah" resource.expects(:isomorphic?).returns(true) @catalog.add_resource(resource) @catalog.resource(:file, "other").must equal(resource) @catalog.resource(:file, @basepath+"/something").ref.should == resource.ref end it "should not add an alias for the namevar when the title and name differ on non-isomorphic resource types" do resource = Puppet::Type.type(:file).new :path => @basepath+"/something", :title => "other", :content => "blah" resource.expects(:isomorphic?).returns(false) @catalog.add_resource(resource) @catalog.resource(:file, resource.title).must equal(resource) # We can't use .should here, because the resources respond to that method. raise "Aliased non-isomorphic resource" if @catalog.resource(:file, resource.name) end it "should provide a method to create additional resources that also registers the resource" do args = {:name => "/yay", :ensure => :file} resource = stub 'file', :ref => "File[/yay]", :catalog= => @catalog, :title => "/yay", :[] => "/yay" Puppet::Type.type(:file).expects(:new).with(args).returns(resource) @catalog.create_resource :file, args @catalog.resource("File[/yay]").must equal(resource) end describe "when adding resources with multiple namevars" do before :each do Puppet::Type.newtype(:multiple) do newparam(:color, :namevar => true) newparam(:designation, :namevar => true) def self.title_patterns [ [ /^(\w+) (\w+)$/, [ [:color, lambda{|x| x}], [:designation, lambda{|x| x}] ] ] ] end end end it "should add an alias using the uniqueness key" do @resource = Puppet::Type.type(:multiple).new(:title => "some resource", :color => "red", :designation => "5") @catalog.add_resource(@resource) @catalog.resource(:multiple, "some resource").must == @resource @catalog.resource("Multiple[some resource]").must == @resource @catalog.resource("Multiple[red 5]").must == @resource end it "should conflict with a resource with the same uniqueness key" do @resource = Puppet::Type.type(:multiple).new(:title => "some resource", :color => "red", :designation => "5") @other = Puppet::Type.type(:multiple).new(:title => "another resource", :color => "red", :designation => "5") @catalog.add_resource(@resource) expect { @catalog.add_resource(@other) }.to raise_error(ArgumentError, /Cannot alias Multiple\[another resource\] to \["red", "5"\].*resource \["Multiple", "red", "5"\] already declared/) end it "should conflict when its uniqueness key matches another resource's title" do path = make_absolute("/tmp/foo") @resource = Puppet::Type.type(:file).new(:title => path) @other = Puppet::Type.type(:file).new(:title => "another file", :path => path) @catalog.add_resource(@resource) expect { @catalog.add_resource(@other) }.to raise_error(ArgumentError, /Cannot alias File\[another file\] to \["#{Regexp.escape(path)}"\].*resource \["File", "#{Regexp.escape(path)}"\] already declared/) end it "should conflict when its uniqueness key matches the uniqueness key derived from another resource's title" do @resource = Puppet::Type.type(:multiple).new(:title => "red leader") @other = Puppet::Type.type(:multiple).new(:title => "another resource", :color => "red", :designation => "leader") @catalog.add_resource(@resource) expect { @catalog.add_resource(@other) }.to raise_error(ArgumentError, /Cannot alias Multiple\[another resource\] to \["red", "leader"\].*resource \["Multiple", "red", "leader"\] already declared/) end end end describe "when applying" do before :each do @catalog = Puppet::Resource::Catalog.new("host") @transaction = Puppet::Transaction.new(@catalog, nil, Puppet::Graph::RandomPrioritizer.new) Puppet::Transaction.stubs(:new).returns(@transaction) @transaction.stubs(:evaluate) @transaction.stubs(:for_network_device=) Puppet.settings.stubs(:use) end it "should create and evaluate a transaction" do @transaction.expects(:evaluate) @catalog.apply end it "should return the transaction" do @catalog.apply.should equal(@transaction) end it "should yield the transaction if a block is provided" do @catalog.apply do |trans| trans.should equal(@transaction) end end it "should default to being a host catalog" do - @catalog.host_config.should be_true + @catalog.host_config.should be_truthy end it "should be able to be set to a non-host_config" do @catalog.host_config = false - @catalog.host_config.should be_false + @catalog.host_config.should be_falsey end it "should pass supplied tags on to the transaction" do @transaction.expects(:tags=).with(%w{one two}) @catalog.apply(:tags => %w{one two}) end it "should set ignoreschedules on the transaction if specified in apply()" do @transaction.expects(:ignoreschedules=).with(true) @catalog.apply(:ignoreschedules => true) end describe "host catalogs" do # super() doesn't work in the setup method for some reason before do @catalog.host_config = true Puppet::Util::Storage.stubs(:store) end it "should initialize the state database before applying a catalog" do Puppet::Util::Storage.expects(:load) # Short-circuit the apply, so we know we're loading before the transaction Puppet::Transaction.expects(:new).raises ArgumentError proc { @catalog.apply }.should raise_error(ArgumentError) end it "should sync the state database after applying" do Puppet::Util::Storage.expects(:store) @transaction.stubs :any_failed? => false @catalog.apply end end describe "non-host catalogs" do before do @catalog.host_config = false end it "should never send reports" do Puppet[:report] = true Puppet[:summarize] = true @catalog.apply end it "should never modify the state database" do Puppet::Util::Storage.expects(:load).never Puppet::Util::Storage.expects(:store).never @catalog.apply end end end describe "when creating a relationship graph" do before do @catalog = Puppet::Resource::Catalog.new("host") end it "should get removed when the catalog is cleaned up" do @catalog.relationship_graph.expects(:clear) @catalog.clear @catalog.instance_variable_get("@relationship_graph").should be_nil end end describe "when writing dot files" do before do @catalog = Puppet::Resource::Catalog.new("host") @name = :test @file = File.join(Puppet[:graphdir], @name.to_s + ".dot") end it "should only write when it is a host catalog" do File.expects(:open).with(@file).never @catalog.host_config = false Puppet[:graph] = true @catalog.write_graph(@name) end end describe "when indirecting" do before do @real_indirection = Puppet::Resource::Catalog.indirection @indirection = stub 'indirection', :name => :catalog end it "should use the value of the 'catalog_terminus' setting to determine its terminus class" do # Puppet only checks the terminus setting the first time you ask # so this returns the object to the clean state # at the expense of making this test less pure Puppet::Resource::Catalog.indirection.reset_terminus_class Puppet.settings[:catalog_terminus] = "rest" Puppet::Resource::Catalog.indirection.terminus_class.should == :rest end it "should allow the terminus class to be set manually" do Puppet::Resource::Catalog.indirection.terminus_class = :rest Puppet::Resource::Catalog.indirection.terminus_class.should == :rest end after do @real_indirection.reset_terminus_class end end describe "when converting to yaml" do before do @catalog = Puppet::Resource::Catalog.new("me") @catalog.add_edge("one", "two") end it "should be able to be dumped to yaml" do YAML.dump(@catalog).should be_instance_of(String) end end describe "when converting from yaml" do before do @catalog = Puppet::Resource::Catalog.new("me") @catalog.add_edge("one", "two") text = YAML.dump(@catalog) @newcatalog = YAML.load(text) end it "should get converted back to a catalog" do @newcatalog.should be_instance_of(Puppet::Resource::Catalog) end it "should have all vertices" do - @newcatalog.vertex?("one").should be_true - @newcatalog.vertex?("two").should be_true + @newcatalog.vertex?("one").should be_truthy + @newcatalog.vertex?("two").should be_truthy end it "should have all edges" do - @newcatalog.edge?("one", "two").should be_true + @newcatalog.edge?("one", "two").should be_truthy end end end describe Puppet::Resource::Catalog, "when converting a resource catalog to pson" do include JSONMatchers include PuppetSpec::Compiler it "should validate an empty catalog against the schema" do empty_catalog = compile_to_catalog("") expect(empty_catalog.to_pson).to validate_against('api/schemas/catalog.json') end it "should validate a noop catalog against the schema" do noop_catalog = compile_to_catalog("create_resources('file', {})") expect(noop_catalog.to_pson).to validate_against('api/schemas/catalog.json') end it "should validate a single resource catalog against the schema" do catalog = compile_to_catalog("create_resources('file', {'/etc/foo'=>{'ensure'=>'present'}})") expect(catalog.to_pson).to validate_against('api/schemas/catalog.json') end it "should validate a virtual resource catalog against the schema" do catalog = compile_to_catalog("create_resources('@file', {'/etc/foo'=>{'ensure'=>'present'}})\nrealize(File['/etc/foo'])") expect(catalog.to_pson).to validate_against('api/schemas/catalog.json') end it "should validate a single exported resource catalog against the schema" do catalog = compile_to_catalog("create_resources('@@file', {'/etc/foo'=>{'ensure'=>'present'}})") expect(catalog.to_pson).to validate_against('api/schemas/catalog.json') end it "should validate a two resource catalog against the schema" do catalog = compile_to_catalog("create_resources('notify', {'foo'=>{'message'=>'one'}, 'bar'=>{'message'=>'two'}})") expect(catalog.to_pson).to validate_against('api/schemas/catalog.json') end it "should validate a two parameter class catalog against the schema" do catalog = compile_to_catalog(<<-MANIFEST) class multi_param_class ($one, $two) { notify {'foo': message => "One is $one, two is $two", } } class {'multi_param_class': one => 'hello', two => 'world', } MANIFEST expect(catalog.to_pson).to validate_against('api/schemas/catalog.json') end end describe Puppet::Resource::Catalog, "when converting to pson" do before do @catalog = Puppet::Resource::Catalog.new("myhost") end def pson_output_should @catalog.class.expects(:from_data_hash).with { |hash| yield hash }.returns(:something) end [:name, :version, :classes].each do |param| it "should set its #{param} to the #{param} of the resource" do @catalog.send(param.to_s + "=", "testing") unless @catalog.send(param) pson_output_should { |hash| hash[param.to_s].should == @catalog.send(param) } Puppet::Resource::Catalog.from_data_hash PSON.parse @catalog.to_pson end end it "should convert its resources to a PSON-encoded array and store it as the 'resources' data" do one = stub 'one', :to_data_hash => "one_resource", :ref => "Foo[one]" two = stub 'two', :to_data_hash => "two_resource", :ref => "Foo[two]" @catalog.add_resource(one) @catalog.add_resource(two) # TODO this should really guarantee sort order PSON.parse(@catalog.to_pson,:create_additions => false)['resources'].sort.should == ["one_resource", "two_resource"].sort end it "should convert its edges to a PSON-encoded array and store it as the 'edges' data" do one = stub 'one', :to_data_hash => "one_resource", :ref => 'Foo[one]' two = stub 'two', :to_data_hash => "two_resource", :ref => 'Foo[two]' three = stub 'three', :to_data_hash => "three_resource", :ref => 'Foo[three]' @catalog.add_edge(one, two) @catalog.add_edge(two, three) @catalog.edges_between(one, two )[0].expects(:to_data_hash).returns "one_two_pson" @catalog.edges_between(two, three)[0].expects(:to_data_hash).returns "two_three_pson" PSON.parse(@catalog.to_pson,:create_additions => false)['edges'].sort.should == %w{one_two_pson two_three_pson}.sort end end describe Puppet::Resource::Catalog, "when converting from pson" do before do @data = { 'name' => "myhost" } end it "should create it with the provided name" do @data['version'] = 50 @data['tags'] = %w{one two} @data['classes'] = %w{one two} @data['edges'] = [Puppet::Relationship.new("File[/foo]", "File[/bar]", :event => "one", :callback => "refresh").to_data_hash] @data['resources'] = [Puppet::Resource.new(:file, "/foo").to_data_hash, Puppet::Resource.new(:file, "/bar").to_data_hash] catalog = Puppet::Resource::Catalog.from_data_hash PSON.parse @data.to_pson expect(catalog.name).to eq('myhost') expect(catalog.version).to eq(@data['version']) expect(catalog).to be_tagged("one") expect(catalog).to be_tagged("two") expect(catalog.classes).to eq(@data['classes']) expect(catalog.resources.collect(&:ref)).to eq(["File[/foo]", "File[/bar]"]) expect(catalog.edges.collect(&:event)).to eq(["one"]) expect(catalog.edges[0].source).to eq(catalog.resource(:file, "/foo")) expect(catalog.edges[0].target).to eq(catalog.resource(:file, "/bar")) end it "should fail if the source resource cannot be found" do @data['edges'] = [Puppet::Relationship.new("File[/missing]", "File[/bar]").to_data_hash] @data['resources'] = [Puppet::Resource.new(:file, "/bar").to_data_hash] expect { Puppet::Resource::Catalog.from_data_hash PSON.parse @data.to_pson }.to raise_error(ArgumentError, /Could not find relationship source/) end it "should fail if the target resource cannot be found" do @data['edges'] = [Puppet::Relationship.new("File[/bar]", "File[/missing]").to_data_hash] @data['resources'] = [Puppet::Resource.new(:file, "/bar").to_data_hash] expect { Puppet::Resource::Catalog.from_data_hash PSON.parse @data.to_pson }.to raise_error(ArgumentError, /Could not find relationship target/) end end diff --git a/spec/unit/resource/type_spec.rb b/spec/unit/resource/type_spec.rb index b1ddf847b..54bb30af9 100755 --- a/spec/unit/resource/type_spec.rb +++ b/spec/unit/resource/type_spec.rb @@ -1,772 +1,772 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/resource/type' require 'puppet/pops' require 'matchers/json' describe Puppet::Resource::Type do include JSONMatchers it "should have a 'name' attribute" do Puppet::Resource::Type.new(:hostclass, "foo").name.should == "foo" end [:code, :doc, :line, :file, :resource_type_collection].each do |attr| it "should have a '#{attr}' attribute" do type = Puppet::Resource::Type.new(:hostclass, "foo") type.send(attr.to_s + "=", "yay") type.send(attr).should == "yay" end end [:hostclass, :node, :definition].each do |type| it "should know when it is a #{type}" do - Puppet::Resource::Type.new(type, "foo").send("#{type}?").should be_true + Puppet::Resource::Type.new(type, "foo").send("#{type}?").should be_truthy end end it "should indirect 'resource_type'" do Puppet::Resource::Type.indirection.name.should == :resource_type end it "should default to 'parser' for its terminus class" do Puppet::Resource::Type.indirection.terminus_class.should == :parser end describe "when converting to json" do before do @type = Puppet::Resource::Type.new(:hostclass, "foo") end def from_json(json) Puppet::Resource::Type.from_data_hash(json) end def double_convert Puppet::Resource::Type.from_data_hash(PSON.parse(@type.to_pson)) end it "should include the name and type" do double_convert.name.should == @type.name double_convert.type.should == @type.type end it "should validate with only name and kind" do expect(@type.to_pson).to validate_against('api/schemas/resource_type.json') end it "should validate with all fields set" do @type.set_arguments("one" => nil, "two" => "foo") @type.line = 100 @type.doc = "A weird type" @type.file = "/etc/manifests/thing.pp" @type.parent = "one::two" expect(@type.to_pson).to validate_against('api/schemas/resource_type.json') end it "should include any arguments" do @type.set_arguments("one" => nil, "two" => "foo") double_convert.arguments.should == {"one" => nil, "two" => "foo"} end it "should not include arguments if none are present" do @type.to_pson["arguments"].should be_nil end [:line, :doc, :file, :parent].each do |attr| it "should include #{attr} when set" do @type.send(attr.to_s + "=", "value") double_convert.send(attr).should == "value" end it "should not include #{attr} when not set" do @type.to_pson[attr.to_s].should be_nil end end it "should not include docs if they are empty" do @type.doc = "" @type.to_pson["doc"].should be_nil end end describe "when a node" do it "should allow a regex as its name" do lambda { Puppet::Resource::Type.new(:node, /foo/) }.should_not raise_error end it "should allow an AST::HostName instance as its name" do regex = Puppet::Parser::AST::Regex.new(:value => /foo/) name = Puppet::Parser::AST::HostName.new(:value => regex) lambda { Puppet::Resource::Type.new(:node, name) }.should_not raise_error end it "should match against the regexp in the AST::HostName when a HostName instance is provided" do regex = Puppet::Parser::AST::Regex.new(:value => /\w/) name = Puppet::Parser::AST::HostName.new(:value => regex) node = Puppet::Resource::Type.new(:node, name) - node.match("foo").should be_true + node.match("foo").should be_truthy end it "should return the value of the hostname if provided a string-form AST::HostName instance as the name" do name = Puppet::Parser::AST::HostName.new(:value => "foo") node = Puppet::Resource::Type.new(:node, name) node.name.should == "foo" end describe "and the name is a regex" do it "should have a method that indicates that this is the case" do Puppet::Resource::Type.new(:node, /w/).should be_name_is_regex end it "should set its namespace to ''" do Puppet::Resource::Type.new(:node, /w/).namespace.should == "" end it "should return the regex converted to a string when asked for its name" do Puppet::Resource::Type.new(:node, /ww/).name.should == "ww" end it "should downcase the regex when returning the name as a string" do Puppet::Resource::Type.new(:node, /W/).name.should == "w" end it "should remove non-alpha characters when returning the name as a string" do Puppet::Resource::Type.new(:node, /w*w/).name.should_not include("*") end it "should remove leading dots when returning the name as a string" do Puppet::Resource::Type.new(:node, /.ww/).name.should_not =~ /^\./ end it "should have a method for matching its regex name against a provided name" do Puppet::Resource::Type.new(:node, /.ww/).should respond_to(:match) end it "should return true when its regex matches the provided name" do - Puppet::Resource::Type.new(:node, /\w/).match("foo").should be_true + Puppet::Resource::Type.new(:node, /\w/).match("foo").should be_truthy end it "should return true when its regex matches the provided name" do - Puppet::Resource::Type.new(:node, /\w/).match("foo").should be_true + Puppet::Resource::Type.new(:node, /\w/).match("foo").should be_truthy end it "should return false when its regex does not match the provided name" do - (!!Puppet::Resource::Type.new(:node, /\d/).match("foo")).should be_false + (!!Puppet::Resource::Type.new(:node, /\d/).match("foo")).should be_falsey end it "should return true when its name, as a string, is matched against an equal string" do - Puppet::Resource::Type.new(:node, "foo").match("foo").should be_true + Puppet::Resource::Type.new(:node, "foo").match("foo").should be_truthy end it "should return false when its name is matched against an unequal string" do - Puppet::Resource::Type.new(:node, "foo").match("bar").should be_false + Puppet::Resource::Type.new(:node, "foo").match("bar").should be_falsey end it "should match names insensitive to case" do - Puppet::Resource::Type.new(:node, "fOo").match("foO").should be_true + Puppet::Resource::Type.new(:node, "fOo").match("foO").should be_truthy end end end describe "when initializing" do it "should require a resource super type" do Puppet::Resource::Type.new(:hostclass, "foo").type.should == :hostclass end it "should fail if provided an invalid resource super type" do lambda { Puppet::Resource::Type.new(:nope, "foo") }.should raise_error(ArgumentError) end it "should set its name to the downcased, stringified provided name" do Puppet::Resource::Type.new(:hostclass, "Foo::Bar".intern).name.should == "foo::bar" end it "should set its namespace to the downcased, stringified qualified name for classes" do Puppet::Resource::Type.new(:hostclass, "Foo::Bar::Baz".intern).namespace.should == "foo::bar::baz" end [:definition, :node].each do |type| it "should set its namespace to the downcased, stringified qualified portion of the name for #{type}s" do Puppet::Resource::Type.new(type, "Foo::Bar::Baz".intern).namespace.should == "foo::bar" end end %w{code line file doc}.each do |arg| it "should set #{arg} if provided" do type = Puppet::Resource::Type.new(:hostclass, "foo", arg.to_sym => "something") type.send(arg).should == "something" end end it "should set any provided arguments with the keys as symbols" do type = Puppet::Resource::Type.new(:hostclass, "foo", :arguments => {:foo => "bar", :baz => "biz"}) type.should be_valid_parameter("foo") type.should be_valid_parameter("baz") end it "should set any provided arguments with they keys as strings" do type = Puppet::Resource::Type.new(:hostclass, "foo", :arguments => {"foo" => "bar", "baz" => "biz"}) type.should be_valid_parameter(:foo) type.should be_valid_parameter(:baz) end it "should function if provided no arguments" do type = Puppet::Resource::Type.new(:hostclass, "foo") type.should_not be_valid_parameter(:foo) end end describe "when testing the validity of an attribute" do it "should return true if the parameter was typed at initialization" do Puppet::Resource::Type.new(:hostclass, "foo", :arguments => {"foo" => "bar"}).should be_valid_parameter("foo") end it "should return true if it is a metaparam" do Puppet::Resource::Type.new(:hostclass, "foo").should be_valid_parameter("require") end it "should return true if the parameter is named 'name'" do Puppet::Resource::Type.new(:hostclass, "foo").should be_valid_parameter("name") end it "should return false if it is not a metaparam and was not provided at initialization" do Puppet::Resource::Type.new(:hostclass, "foo").should_not be_valid_parameter("yayness") end end describe "when setting its parameters in the scope" do def variable_expression(name) varexpr = Puppet::Pops::Model::Factory.QNAME(name).var().current Puppet::Parser::AST::PopsBridge::Expression.new(:value => varexpr) end before do @scope = Puppet::Parser::Scope.new(Puppet::Parser::Compiler.new(Puppet::Node.new("foo")), :source => stub("source")) @resource = Puppet::Parser::Resource.new(:foo, "bar", :scope => @scope) @type = Puppet::Resource::Type.new(:definition, "foo") @resource.environment.known_resource_types.add @type end ['module_name', 'name', 'title'].each do |variable| it "should allow #{variable} to be evaluated as param default" do @type.instance_eval { @module_name = "bar" } @type.set_arguments :foo => variable_expression(variable) @type.set_resource_parameters(@resource, @scope) @scope['foo'].should == 'bar' end end # this test is to clarify a crazy edge case # if you specify these special names as params, the resource # will override the special variables it "should allow the resource to override defaults" do @type.set_arguments :name => nil @resource[:name] = 'foobar' @type.set_arguments :foo => variable_expression('name') @type.set_resource_parameters(@resource, @scope) @scope['foo'].should == 'foobar' end it "should set each of the resource's parameters as variables in the scope" do @type.set_arguments :foo => nil, :boo => nil @resource[:foo] = "bar" @resource[:boo] = "baz" @type.set_resource_parameters(@resource, @scope) @scope['foo'].should == "bar" @scope['boo'].should == "baz" end it "should set the variables as strings" do @type.set_arguments :foo => nil @resource[:foo] = "bar" @type.set_resource_parameters(@resource, @scope) @scope['foo'].should == "bar" end it "should fail if any of the resource's parameters are not valid attributes" do @type.set_arguments :foo => nil @resource[:boo] = "baz" lambda { @type.set_resource_parameters(@resource, @scope) }.should raise_error(Puppet::ParseError) end it "should evaluate and set its default values as variables for parameters not provided by the resource" do @type.set_arguments :foo => Puppet::Parser::AST::Leaf.new(:value => "something") @type.set_resource_parameters(@resource, @scope) @scope['foo'].should == "something" end it "should set all default values as parameters in the resource" do @type.set_arguments :foo => Puppet::Parser::AST::Leaf.new(:value => "something") @type.set_resource_parameters(@resource, @scope) @resource[:foo].should == "something" end it "should fail if the resource does not provide a value for a required argument" do @type.set_arguments :foo => nil lambda { @type.set_resource_parameters(@resource, @scope) }.should raise_error(Puppet::ParseError) end it "should set the resource's title as a variable if not otherwise provided" do @type.set_resource_parameters(@resource, @scope) @scope['title'].should == "bar" end it "should set the resource's name as a variable if not otherwise provided" do @type.set_resource_parameters(@resource, @scope) @scope['name'].should == "bar" end it "should set its module name in the scope if available" do @type.instance_eval { @module_name = "mymod" } @type.set_resource_parameters(@resource, @scope) @scope["module_name"].should == "mymod" end it "should set its caller module name in the scope if available" do @scope.expects(:parent_module_name).returns "mycaller" @type.set_resource_parameters(@resource, @scope) @scope["caller_module_name"].should == "mycaller" end end describe "when describing and managing parent classes" do before do environment = Puppet::Node::Environment.create(:testing, []) @krt = environment.known_resource_types @parent = Puppet::Resource::Type.new(:hostclass, "bar") @krt.add @parent @child = Puppet::Resource::Type.new(:hostclass, "foo", :parent => "bar") @krt.add @child @scope = Puppet::Parser::Scope.new(Puppet::Parser::Compiler.new(Puppet::Node.new("foo", :environment => environment))) end it "should be able to define a parent" do Puppet::Resource::Type.new(:hostclass, "foo", :parent => "bar") end it "should use the code collection to find the parent resource type" do @child.parent_type(@scope).should equal(@parent) end it "should be able to find parent nodes" do parent = Puppet::Resource::Type.new(:node, "bar") @krt.add parent child = Puppet::Resource::Type.new(:node, "foo", :parent => "bar") @krt.add child child.parent_type(@scope).should equal(parent) end it "should cache a reference to the parent type" do @krt.stubs(:hostclass).with("foo::bar").returns nil @krt.expects(:hostclass).with("bar").once.returns @parent @child.parent_type(@scope) @child.parent_type end it "should correctly state when it is another type's child" do @child.parent_type(@scope) @child.should be_child_of(@parent) end it "should be considered the child of a parent's parent" do @grandchild = Puppet::Resource::Type.new(:hostclass, "baz", :parent => "foo") @krt.add @grandchild @child.parent_type(@scope) @grandchild.parent_type(@scope) @grandchild.should be_child_of(@parent) end it "should correctly state when it is not another type's child" do @notchild = Puppet::Resource::Type.new(:hostclass, "baz") @krt.add @notchild @notchild.should_not be_child_of(@parent) end end describe "when evaluating its code" do before do @compiler = Puppet::Parser::Compiler.new(Puppet::Node.new("mynode")) @scope = Puppet::Parser::Scope.new @compiler @resource = Puppet::Parser::Resource.new(:class, "foo", :scope => @scope) # This is so the internal resource lookup works, yo. @compiler.catalog.add_resource @resource @type = Puppet::Resource::Type.new(:hostclass, "foo") @resource.environment.known_resource_types.add @type end it "should add node regex captures to its scope" do @type = Puppet::Resource::Type.new(:node, /f(\w)o(.*)$/) match = @type.match('foo') code = stub 'code' @type.stubs(:code).returns code subscope = stub 'subscope', :compiler => @compiler @scope.expects(:newscope).with(:source => @type, :namespace => '', :resource => @resource).returns subscope elevel = 876 subscope.expects(:ephemeral_level).returns elevel subscope.expects(:ephemeral_from).with(match, nil, nil).returns subscope code.expects(:safeevaluate).with(subscope) subscope.expects(:unset_ephemeral_var).with(elevel) # Just to keep the stub quiet about intermediate calls @type.expects(:set_resource_parameters).with(@resource, subscope) @type.evaluate_code(@resource) end it "should add hostclass names to the classes list" do @type.evaluate_code(@resource) @compiler.catalog.classes.should be_include("foo") end it "should not add defined resource names to the classes list" do @type = Puppet::Resource::Type.new(:definition, "foo") @type.evaluate_code(@resource) @compiler.catalog.classes.should_not be_include("foo") end it "should set all of its parameters in a subscope" do subscope = stub 'subscope', :compiler => @compiler @scope.expects(:newscope).with(:source => @type, :namespace => 'foo', :resource => @resource).returns subscope @type.expects(:set_resource_parameters).with(@resource, subscope) @type.evaluate_code(@resource) end it "should not create a subscope for the :main class" do @resource.stubs(:title).returns(:main) @type.expects(:subscope).never @type.expects(:set_resource_parameters).with(@resource, @scope) @type.evaluate_code(@resource) end it "should store the class scope" do @type.evaluate_code(@resource) @scope.class_scope(@type).should be_instance_of(@scope.class) end it "should still create a scope but not store it if the type is a definition" do @type = Puppet::Resource::Type.new(:definition, "foo") @type.evaluate_code(@resource) @scope.class_scope(@type).should be_nil end it "should evaluate the AST code if any is provided" do code = stub 'code' @type.stubs(:code).returns code subscope = stub_everything("subscope", :compiler => @compiler) @scope.stubs(:newscope).returns subscope code.expects(:safeevaluate).with subscope @type.evaluate_code(@resource) end it "should noop if there is no code" do @type.expects(:code).returns nil @type.evaluate_code(@resource) end describe "and it has a parent class" do before do @parent_type = Puppet::Resource::Type.new(:hostclass, "parent") @type.parent = "parent" @parent_resource = Puppet::Parser::Resource.new(:class, "parent", :scope => @scope) @compiler.add_resource @scope, @parent_resource @type.resource_type_collection = @scope.known_resource_types @type.resource_type_collection.add @parent_type end it "should evaluate the parent's resource" do @type.parent_type(@scope) @type.evaluate_code(@resource) @scope.class_scope(@parent_type).should_not be_nil end it "should not evaluate the parent's resource if it has already been evaluated" do @parent_resource.evaluate @type.parent_type(@scope) @parent_resource.expects(:evaluate).never @type.evaluate_code(@resource) end it "should use the parent's scope as its base scope" do @type.parent_type(@scope) @type.evaluate_code(@resource) @scope.class_scope(@type).parent.object_id.should == @scope.class_scope(@parent_type).object_id end end describe "and it has a parent node" do before do @type = Puppet::Resource::Type.new(:node, "foo") @parent_type = Puppet::Resource::Type.new(:node, "parent") @type.parent = "parent" @parent_resource = Puppet::Parser::Resource.new(:node, "parent", :scope => @scope) @compiler.add_resource @scope, @parent_resource @type.resource_type_collection = @scope.known_resource_types @type.resource_type_collection.add(@parent_type) end it "should evaluate the parent's resource" do @type.parent_type(@scope) @type.evaluate_code(@resource) @scope.class_scope(@parent_type).should_not be_nil end it "should not evaluate the parent's resource if it has already been evaluated" do @parent_resource.evaluate @type.parent_type(@scope) @parent_resource.expects(:evaluate).never @type.evaluate_code(@resource) end it "should use the parent's scope as its base scope" do @type.parent_type(@scope) @type.evaluate_code(@resource) @scope.class_scope(@type).parent.object_id.should == @scope.class_scope(@parent_type).object_id end end end describe "when creating a resource" do before do env = Puppet::Node::Environment.create('env', []) @node = Puppet::Node.new("foo", :environment => env) @compiler = Puppet::Parser::Compiler.new(@node) @scope = Puppet::Parser::Scope.new(@compiler) @top = Puppet::Resource::Type.new :hostclass, "top" @middle = Puppet::Resource::Type.new :hostclass, "middle", :parent => "top" @code = env.known_resource_types @code.add @top @code.add @middle end it "should create a resource instance" do @top.ensure_in_catalog(@scope).should be_instance_of(Puppet::Parser::Resource) end it "should set its resource type to 'class' when it is a hostclass" do Puppet::Resource::Type.new(:hostclass, "top").ensure_in_catalog(@scope).type.should == "Class" end it "should set its resource type to 'node' when it is a node" do Puppet::Resource::Type.new(:node, "top").ensure_in_catalog(@scope).type.should == "Node" end it "should fail when it is a definition" do lambda { Puppet::Resource::Type.new(:definition, "top").ensure_in_catalog(@scope) }.should raise_error(ArgumentError) end it "should add the created resource to the scope's catalog" do @top.ensure_in_catalog(@scope) @compiler.catalog.resource(:class, "top").should be_instance_of(Puppet::Parser::Resource) end it "should add specified parameters to the resource" do @top.ensure_in_catalog(@scope, {'one'=>'1', 'two'=>'2'}) @compiler.catalog.resource(:class, "top")['one'].should == '1' @compiler.catalog.resource(:class, "top")['two'].should == '2' end it "should not require params for a param class" do @top.ensure_in_catalog(@scope, {}) @compiler.catalog.resource(:class, "top").should be_instance_of(Puppet::Parser::Resource) end it "should evaluate the parent class if one exists" do @middle.ensure_in_catalog(@scope) @compiler.catalog.resource(:class, "top").should be_instance_of(Puppet::Parser::Resource) end it "should evaluate the parent class if one exists" do @middle.ensure_in_catalog(@scope, {}) @compiler.catalog.resource(:class, "top").should be_instance_of(Puppet::Parser::Resource) end it "should fail if you try to create duplicate class resources" do othertop = Puppet::Parser::Resource.new(:class, 'top',:source => @source, :scope => @scope ) # add the same class resource to the catalog @compiler.catalog.add_resource(othertop) lambda { @top.ensure_in_catalog(@scope, {}) }.should raise_error(Puppet::Resource::Catalog::DuplicateResourceError) end it "should fail to evaluate if a parent class is defined but cannot be found" do othertop = Puppet::Resource::Type.new :hostclass, "something", :parent => "yay" @code.add othertop lambda { othertop.ensure_in_catalog(@scope) }.should raise_error(Puppet::ParseError) end it "should not create a new resource if one already exists" do @compiler.catalog.expects(:resource).with(:class, "top").returns("something") @compiler.catalog.expects(:add_resource).never @top.ensure_in_catalog(@scope) end it "should return the existing resource when not creating a new one" do @compiler.catalog.expects(:resource).with(:class, "top").returns("something") @compiler.catalog.expects(:add_resource).never @top.ensure_in_catalog(@scope).should == "something" end it "should not create a new parent resource if one already exists and it has a parent class" do @top.ensure_in_catalog(@scope) top_resource = @compiler.catalog.resource(:class, "top") @middle.ensure_in_catalog(@scope) @compiler.catalog.resource(:class, "top").should equal(top_resource) end # #795 - tag before evaluation. it "should tag the catalog with the resource tags when it is evaluated" do @middle.ensure_in_catalog(@scope) @compiler.catalog.should be_tagged("middle") end it "should tag the catalog with the parent class tags when it is evaluated" do @middle.ensure_in_catalog(@scope) @compiler.catalog.should be_tagged("top") end end describe "when merging code from another instance" do def code(str) factory = Puppet::Pops::Model::Factory.literal(str) end it "should fail unless it is a class" do lambda { Puppet::Resource::Type.new(:node, "bar").merge("foo") }.should raise_error(Puppet::Error) end it "should fail unless the source instance is a class" do dest = Puppet::Resource::Type.new(:hostclass, "bar") source = Puppet::Resource::Type.new(:node, "foo") lambda { dest.merge(source) }.should raise_error(Puppet::Error) end it "should fail if both classes have different parent classes" do code = Puppet::Resource::TypeCollection.new("env") {"a" => "b", "c" => "d"}.each do |parent, child| code.add Puppet::Resource::Type.new(:hostclass, parent) code.add Puppet::Resource::Type.new(:hostclass, child, :parent => parent) end lambda { code.hostclass("b").merge(code.hostclass("d")) }.should raise_error(Puppet::Error) end it "should fail if it's named 'main' and 'freeze_main' is enabled" do Puppet.settings[:freeze_main] = true code = Puppet::Resource::TypeCollection.new("env") code.add Puppet::Resource::Type.new(:hostclass, "") other = Puppet::Resource::Type.new(:hostclass, "") lambda { code.hostclass("").merge(other) }.should raise_error(Puppet::Error) end it "should copy the other class's parent if it has not parent" do dest = Puppet::Resource::Type.new(:hostclass, "bar") parent = Puppet::Resource::Type.new(:hostclass, "parent") source = Puppet::Resource::Type.new(:hostclass, "foo", :parent => "parent") dest.merge(source) dest.parent.should == "parent" end it "should copy the other class's documentation as its docs if it has no docs" do dest = Puppet::Resource::Type.new(:hostclass, "bar") source = Puppet::Resource::Type.new(:hostclass, "foo", :doc => "yayness") dest.merge(source) dest.doc.should == "yayness" end it "should append the other class's docs to its docs if it has any" do dest = Puppet::Resource::Type.new(:hostclass, "bar", :doc => "fooness") source = Puppet::Resource::Type.new(:hostclass, "foo", :doc => "yayness") dest.merge(source) dest.doc.should == "foonessyayness" end it "should set the other class's code as its code if it has none" do dest = Puppet::Resource::Type.new(:hostclass, "bar") source = Puppet::Resource::Type.new(:hostclass, "foo", :code => code("bar")) dest.merge(source) dest.code.value.should == "bar" end it "should append the other class's code to its code if it has any" do # PUP-3274, the code merging at the top still uses AST::BlockExpression # But does not do mutating changes to code blocks, instead a new block is created # with references to the two original blocks. # TODO: fix this when the code merging is changed at the very top in 4x. # dcode = Puppet::Parser::AST::BlockExpression.new(:children => [code("dest")]) dest = Puppet::Resource::Type.new(:hostclass, "bar", :code => dcode) scode = Puppet::Parser::AST::BlockExpression.new(:children => [code("source")]) source = Puppet::Resource::Type.new(:hostclass, "foo", :code => scode) dest.merge(source) dest.code.children.collect { |l| l.children[0].value }.should == %w{dest source} end end end diff --git a/spec/unit/resource_spec.rb b/spec/unit/resource_spec.rb index 0e20333f6..f26a92c05 100755 --- a/spec/unit/resource_spec.rb +++ b/spec/unit/resource_spec.rb @@ -1,1001 +1,1001 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/resource' describe Puppet::Resource do include PuppetSpec::Files let(:basepath) { make_absolute("/somepath") } let(:environment) { Puppet::Node::Environment.create(:testing, []) } [: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 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 not interpret the title as a reference if the type is a non component or whit reference" do ref = Puppet::Resource.new("Notify", "foo::bar[baz]") ref.type.should == "Notify" ref.title.should =="foo::bar[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") }.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}) }.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}) }.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 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 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") environment.known_resource_types.add @type end it "should set its type to the capitalized type name" do Puppet::Resource.new("foo::bar", "/my/file", :environment => environment).type.should == "Foo::Bar" end it "should be able to find the resource type" do Puppet::Resource.new("foo::bar", "/my/file", :environment => environment).resource_type.should equal(@type) end it "should set its title to the provided title" do Puppet::Resource.new("foo::bar", "/my/file", :environment => environment).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") environment.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", :environment => environment).title.should == "Foo::Bar" end it "should be able to find the resource type" do Puppet::Resource.new("class", "foo::bar", :environment => environment).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, "") environment.known_resource_types.add @type Puppet::Resource.new("class", "", :environment => environment).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, "") environment.known_resource_types.add @type Puppet::Resource.new("class", :main, :environment => environment).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") environment.known_resource_types.add type resource = Puppet::Resource.new("foobar", "/my/file", :environment => environment) 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 setting default parameters" do let(:foo_node) { Puppet::Node.new('foo', :environment => environment) } let(:compiler) { Puppet::Parser::Compiler.new(foo_node) } let(:scope) { Puppet::Parser::Scope.new(compiler) } def ast_leaf(value) Puppet::Parser::AST::Leaf.new({:value => value}) end it "should fail when asked to set default values and it is not a parser resource" do environment.known_resource_types.add( Puppet::Resource::Type.new(:definition, "default_param", :arguments => {"a" => ast_leaf("default")}) ) resource = Puppet::Resource.new("default_param", "name", :environment => environment) lambda { resource.set_default_parameters(scope) }.should raise_error(Puppet::DevError) end it "should evaluate and set any default values when no value is provided" do environment.known_resource_types.add( Puppet::Resource::Type.new(:definition, "default_param", :arguments => {"a" => ast_leaf("a_default_value")}) ) resource = Puppet::Parser::Resource.new("default_param", "name", :scope => scope) resource.set_default_parameters(scope) resource["a"].should == "a_default_value" end it "should skip attributes with no default value" do environment.known_resource_types.add( Puppet::Resource::Type.new(:definition, "no_default_param", :arguments => {"a" => ast_leaf("a_default_value")}) ) resource = Puppet::Parser::Resource.new("no_default_param", "name", :scope => scope) lambda { resource.set_default_parameters(scope) }.should_not raise_error end it "should return the list of default parameters set" do environment.known_resource_types.add( Puppet::Resource::Type.new(:definition, "default_param", :arguments => {"a" => ast_leaf("a_default_value")}) ) resource = Puppet::Parser::Resource.new("default_param", "name", :scope => scope) resource.set_default_parameters(scope).should == ["a"] end describe "when the resource type is :hostclass" do let(:environment_name) { "testing env" } let(:fact_values) { { :a => 1 } } let(:port) { Puppet::Parser::AST::Leaf.new(:value => '80') } let(:apache) { Puppet::Resource::Type.new(:hostclass, 'apache', :arguments => { 'port' => port }) } before do environment.known_resource_types.add(apache) scope.stubs(:host).returns('host') scope.stubs(:environment).returns(environment) scope.stubs(:facts).returns(Puppet::Node::Facts.new("facts", fact_values)) end context "when no value is provided" do let(:resource) do Puppet::Parser::Resource.new("class", "apache", :scope => scope) end it "should query the data_binding terminus using a namespaced key" do Puppet::DataBinding.indirection.expects(:find).with( 'apache::port', all_of(has_key(:environment), has_key(:variables))) resource.set_default_parameters(scope) end it "should use the value from the data_binding terminus" do Puppet::DataBinding.indirection.expects(:find).returns('443') resource.set_default_parameters(scope) resource[:port].should == '443' end it "should use the default value if the data_binding terminus returns nil" do Puppet::DataBinding.indirection.expects(:find).returns(nil) resource.set_default_parameters(scope) resource[:port].should == '80' end it "should fail with error message about data binding on a hiera failure" do Puppet::DataBinding.indirection.expects(:find).raises(Puppet::DataBinding::LookupError, 'Forgettabotit') expect { resource.set_default_parameters(scope) }.to raise_error(Puppet::Error, /Error from DataBinding 'hiera' while looking up 'apache::port':.*Forgettabotit/) end end context "when a value is provided" do let(:port_parameter) do Puppet::Parser::Resource::Param.new( { :name => 'port', :value => '8080' } ) end let(:resource) do Puppet::Parser::Resource.new("class", "apache", :scope => scope, :parameters => [port_parameter]) end it "should not query the data_binding terminus" do Puppet::DataBinding.indirection.expects(:find).never resource.set_default_parameters(scope) end it "should not query the injector" do compiler.injector.expects(:find).never resource.set_default_parameters(scope) end it "should use the value provided" do Puppet::DataBinding.indirection.expects(:find).never resource.set_default_parameters(scope).should == [] resource[:port].should == '8080' end end end end describe "when validating all required parameters are present" do it "should be able to validate that all required parameters are present" do environment.known_resource_types.add( Puppet::Resource::Type.new(:definition, "required_param", :arguments => {"a" => nil}) ) lambda { Puppet::Resource.new("required_param", "name", :environment => environment).validate_complete }.should raise_error(Puppet::ParseError) end it "should not fail when all required parameters are present" do environment.known_resource_types.add( Puppet::Resource::Type.new(:definition, "no_required_param") ) resource = Puppet::Resource.new("no_required_param", "name", :environment => environment) resource["a"] = "meh" lambda { resource.validate_complete }.should_not raise_error end it "should not validate against builtin types" do lambda { Puppet::Resource.new("file", "/bar").validate_complete }.should_not raise_error end 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"}) }.to raise_error end it "should fail if the resource type cannot be resolved" do 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") environment.known_resource_types.add type Puppet::Resource.new("foobar", "/my/file", :environment => environment).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}) environment.known_resource_types.add type Puppet::Resource.new("foobar", "/my/file", :environment => environment).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" }.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 a native type" do before do @resource = Puppet::Resource.new("file", "/my/file") @resource["one"] = "test" @resource["two"] = "other" end # PUP-3272, needs to work becuse serialization is not only to network # it "should produce an equivalent yaml object" do text = @resource.render('yaml') newresource = Puppet::Resource.convert_from('yaml', text) newresource.should equal_resource_attributes_of(@resource) end # PUP-3272, since serialization to network is done in pson, not yaml it "should produce an equivalent pson object" do text = @resource.render('pson') newresource = Puppet::Resource.convert_from('pson', text) newresource.should equal_resource_attributes_of(@resource) end end describe "when serializing a defined type" do before do type = Puppet::Resource::Type.new(:definition, "foo::bar") environment.known_resource_types.add type @resource = Puppet::Resource.new('foo::bar', 'xyzzy', :environment => environment) @resource['one'] = 'test' @resource['two'] = 'other' @resource.resource_type end it "doesn't include transient instance variables (#4506)" do expect(@resource.to_yaml_properties).to_not include(:@rstype) end it "produces an equivalent pson object" do text = @resource.render('pson') newresource = Puppet::Resource.convert_from('pson', text) newresource.should equal_resource_attributes_of(@resource) 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" 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 Yaml for Hiera" do before do @resource = Puppet::Resource.new("one::two", "/my/file", :parameters => { :noop => true, :foo => %w{one two}, :ensure => 'present', } ) end it "should align and sort to attributes with ensure first" do @resource.to_hierayaml.should == <<-HEREDOC.gsub(/^\s{8}/, '') /my/file: ensure: 'present' foo : ['one', 'two'] noop : 'true' HEREDOC end end describe "when converting to pson" do # 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_data_hash(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_data_hash(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_data_hash(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_data_hash(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_data_hash(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_data_hash(PSON.parse(resource.to_pson)).exported?.should be_true + Puppet::Resource.from_data_hash(PSON.parse(resource.to_pson)).exported?.should be_truthy end it "should set 'exported' to false if no value is set" do resource = Puppet::Resource.new("File", "/foo") - Puppet::Resource.from_data_hash(PSON.parse(resource.to_pson)).exported?.should be_false + Puppet::Resource.from_data_hash(PSON.parse(resource.to_pson)).exported?.should be_falsey 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_data_hash(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_data_hash(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_data_hash(PSON.parse(resource.to_pson)) result[:requires].should == [ "File[/bar]", "File[/baz]" ] end end describe "when converting from 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_data_hash(@data).type.should == "File" end it "should set its title to the provided title" do Puppet::Resource.from_data_hash(@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_data_hash(@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_data_hash(@data).file.should == "/foo/bar" end it "should set its line to the provided line" do @data['line'] = 50 Puppet::Resource.from_data_hash(@data).line.should == 50 end it "should 'exported' to true if set in the pson data" do @data['exported'] = true - Puppet::Resource.from_data_hash(@data).exported.should be_true + Puppet::Resource.from_data_hash(@data).exported.should be_truthy end it "should 'exported' to false if not set in the pson data" do - Puppet::Resource.from_data_hash(@data).exported.should be_false + Puppet::Resource.from_data_hash(@data).exported.should be_falsey end it "should fail if no title is provided" do @data.delete('title') expect { Puppet::Resource.from_data_hash(@data) }.to raise_error(ArgumentError) end it "should fail if no type is provided" do @data.delete('type') expect { Puppet::Resource.from_data_hash(@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_data_hash(@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_data_hash(@data) resource['foo'].should == %w{one} end end it "implements copy_as_resource" do resource = Puppet::Resource.new("file", "/my/file") resource.copy_as_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 '#parse_title' do describe 'with a composite namevar' do before do Puppet::Type.newtype(:composite) do newparam(:name) newparam(:value) # Configure two title patterns to match a title that is either # separated with a colon or exclamation point. The first capture # will be used for the :name param, and the second capture will be # used for the :value param. def self.title_patterns identity = lambda {|x| x } reverse = lambda {|x| x.reverse } [ [ /^(.*?):(.*?)$/, [ [:name, identity], [:value, identity], ] ], [ /^(.*?)!(.*?)$/, [ [:name, reverse], [:value, reverse], ] ], ] end end end describe "with no matching title patterns" do subject { Puppet::Resource.new(:composite, 'unmatching title')} it "should raise an exception if no title patterns match" do expect do subject.to_hash end.to raise_error(Puppet::Error, /No set of title patterns matched/) end end describe "with a matching title pattern" do subject { Puppet::Resource.new(:composite, 'matching:title') } it "should not raise an exception if there was a match" do expect do subject.to_hash end.to_not raise_error end it "should set the resource parameters from the parsed title values" do h = subject.to_hash h[:name].should == 'matching' h[:value].should == 'title' end end describe "and multiple title patterns" do subject { Puppet::Resource.new(:composite, 'matching!title') } it "should use the first title pattern that matches" do h = subject.to_hash h[:name].should == 'gnihctam' h[:value].should == 'eltit' end end end end describe "#prune_parameters" do before do Puppet::Type.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/semver_spec.rb b/spec/unit/semver_spec.rb index d4fc5da04..a59292cb3 100644 --- a/spec/unit/semver_spec.rb +++ b/spec/unit/semver_spec.rb @@ -1,303 +1,303 @@ require 'spec_helper' require 'semver' describe SemVer do describe 'MAX should be +Infinity' do SemVer::MAX.major.infinite?.should == 1 end describe '::valid?' do it 'should validate basic version strings' do %w[ 0.0.0 999.999.999 v0.0.0 v999.999.999 ].each do |vstring| - SemVer.valid?(vstring).should be_true + SemVer.valid?(vstring).should be_truthy end end it 'should validate special version strings' do %w[ 0.0.0-foo 999.999.999-bar v0.0.0-a v999.999.999-beta ].each do |vstring| - SemVer.valid?(vstring).should be_true + SemVer.valid?(vstring).should be_truthy end end it 'should fail to validate invalid version strings' do %w[ nope 0.0foo 999.999 x0.0.0 z.z.z 1.2.3beta 1.x.y ].each do |vstring| - SemVer.valid?(vstring).should be_false + SemVer.valid?(vstring).should be_falsey end end end describe '::pre' do it 'should append a dash when no dash appears in the string' do SemVer.pre('1.2.3').should == '1.2.3-' end it 'should not append a dash when a dash appears in the string' do SemVer.pre('1.2.3-a').should == '1.2.3-a' end end describe '::find_matching' do before :all do @versions = %w[ 0.0.1 0.0.2 1.0.0-rc1 1.0.0-rc2 1.0.0 1.0.1 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.2.0 1.2.1 2.0.0-rc1 ].map { |v| SemVer.new(v) } end it 'should match exact versions by string' do @versions.each do |version| SemVer.find_matching(version, @versions).should == version end end it 'should return nil if no versions match' do %w[ 3.0.0 2.0.0-rc2 1.0.0-alpha ].each do |v| SemVer.find_matching(v, @versions).should be_nil end end it 'should find the greatest match for partial versions' do SemVer.find_matching('1.0', @versions).should == 'v1.0.1' SemVer.find_matching('1.1', @versions).should == 'v1.1.4' SemVer.find_matching('1', @versions).should == 'v1.2.1' SemVer.find_matching('2', @versions).should == 'v2.0.0-rc1' SemVer.find_matching('2.1', @versions).should == nil end it 'should find the greatest match for versions with placeholders' do SemVer.find_matching('1.0.x', @versions).should == 'v1.0.1' SemVer.find_matching('1.1.x', @versions).should == 'v1.1.4' SemVer.find_matching('1.x', @versions).should == 'v1.2.1' SemVer.find_matching('1.x.x', @versions).should == 'v1.2.1' SemVer.find_matching('2.x', @versions).should == 'v2.0.0-rc1' SemVer.find_matching('2.x.x', @versions).should == 'v2.0.0-rc1' SemVer.find_matching('2.1.x', @versions).should == nil end end describe '::[]' do it "should produce expected ranges" do tests = { '1.2.3-alpha' => SemVer.new('v1.2.3-alpha') .. SemVer.new('v1.2.3-alpha'), '1.2.3' => SemVer.new('v1.2.3-') .. SemVer.new('v1.2.3'), '>1.2.3-alpha' => SemVer.new('v1.2.3-alpha-') .. SemVer::MAX, '>1.2.3' => SemVer.new('v1.2.4-') .. SemVer::MAX, '<1.2.3-alpha' => SemVer::MIN ... SemVer.new('v1.2.3-alpha'), '<1.2.3' => SemVer::MIN ... SemVer.new('v1.2.3-'), '>=1.2.3-alpha' => SemVer.new('v1.2.3-alpha') .. SemVer::MAX, '>=1.2.3' => SemVer.new('v1.2.3-') .. SemVer::MAX, '<=1.2.3-alpha' => SemVer::MIN .. SemVer.new('v1.2.3-alpha'), '<=1.2.3' => SemVer::MIN .. SemVer.new('v1.2.3'), '>1.2.3-a <1.2.3-b' => SemVer.new('v1.2.3-a-') ... SemVer.new('v1.2.3-b'), '>1.2.3 <1.2.5' => SemVer.new('v1.2.4-') ... SemVer.new('v1.2.5-'), '>=1.2.3-a <= 1.2.3-b' => SemVer.new('v1.2.3-a') .. SemVer.new('v1.2.3-b'), '>=1.2.3 <=1.2.5' => SemVer.new('v1.2.3-') .. SemVer.new('v1.2.5'), '1.2.3-a - 2.3.4-b' => SemVer.new('v1.2.3-a') .. SemVer.new('v2.3.4-b'), '1.2.3 - 2.3.4' => SemVer.new('v1.2.3-') .. SemVer.new('v2.3.4'), '~1.2.3' => SemVer.new('v1.2.3-') ... SemVer.new('v1.3.0-'), '~1.2' => SemVer.new('v1.2.0-') ... SemVer.new('v2.0.0-'), '~1' => SemVer.new('v1.0.0-') ... SemVer.new('v2.0.0-'), '1.2.x' => SemVer.new('v1.2.0') ... SemVer.new('v1.3.0-'), '1.x' => SemVer.new('v1.0.0') ... SemVer.new('v2.0.0-'), } tests.each do |vstring, expected| SemVer[vstring].should == expected end end it "should suit up" do suitability = { [ '1.2.3', 'v1.2.2' ] => false, [ '>=1.2.3', 'v1.2.2' ] => false, [ '<=1.2.3', 'v1.2.2' ] => true, [ '>= 1.2.3', 'v1.2.2' ] => false, [ '<= 1.2.3', 'v1.2.2' ] => true, [ '1.2.3 - 1.2.4', 'v1.2.2' ] => false, [ '~1.2.3', 'v1.2.2' ] => false, [ '~1.2', 'v1.2.2' ] => true, [ '~1', 'v1.2.2' ] => true, [ '1.2.x', 'v1.2.2' ] => true, [ '1.x', 'v1.2.2' ] => true, [ '1.2.3', 'v1.2.3-alpha' ] => true, [ '>=1.2.3', 'v1.2.3-alpha' ] => true, [ '<=1.2.3', 'v1.2.3-alpha' ] => true, [ '>= 1.2.3', 'v1.2.3-alpha' ] => true, [ '<= 1.2.3', 'v1.2.3-alpha' ] => true, [ '>1.2.3', 'v1.2.3-alpha' ] => false, [ '<1.2.3', 'v1.2.3-alpha' ] => false, [ '> 1.2.3', 'v1.2.3-alpha' ] => false, [ '< 1.2.3', 'v1.2.3-alpha' ] => false, [ '1.2.3 - 1.2.4', 'v1.2.3-alpha' ] => true, [ '1.2.3 - 1.2.4', 'v1.2.4-alpha' ] => true, [ '1.2.3 - 1.2.4', 'v1.2.5-alpha' ] => false, [ '~1.2.3', 'v1.2.3-alpha' ] => true, [ '~1.2.3', 'v1.3.0-alpha' ] => false, [ '~1.2', 'v1.2.3-alpha' ] => true, [ '~1.2', 'v2.0.0-alpha' ] => false, [ '~1', 'v1.2.3-alpha' ] => true, [ '~1', 'v2.0.0-alpha' ] => false, [ '1.2.x', 'v1.2.3-alpha' ] => true, [ '1.2.x', 'v1.3.0-alpha' ] => false, [ '1.x', 'v1.2.3-alpha' ] => true, [ '1.x', 'v2.0.0-alpha' ] => false, [ '1.2.3', 'v1.2.3' ] => true, [ '>=1.2.3', 'v1.2.3' ] => true, [ '<=1.2.3', 'v1.2.3' ] => true, [ '>= 1.2.3', 'v1.2.3' ] => true, [ '<= 1.2.3', 'v1.2.3' ] => true, [ '1.2.3 - 1.2.4', 'v1.2.3' ] => true, [ '~1.2.3', 'v1.2.3' ] => true, [ '~1.2', 'v1.2.3' ] => true, [ '~1', 'v1.2.3' ] => true, [ '1.2.x', 'v1.2.3' ] => true, [ '1.x', 'v1.2.3' ] => true, [ '1.2.3', 'v1.2.4' ] => false, [ '>=1.2.3', 'v1.2.4' ] => true, [ '<=1.2.3', 'v1.2.4' ] => false, [ '>= 1.2.3', 'v1.2.4' ] => true, [ '<= 1.2.3', 'v1.2.4' ] => false, [ '1.2.3 - 1.2.4', 'v1.2.4' ] => true, [ '~1.2.3', 'v1.2.4' ] => true, [ '~1.2', 'v1.2.4' ] => true, [ '~1', 'v1.2.4' ] => true, [ '1.2.x', 'v1.2.4' ] => true, [ '1.x', 'v1.2.4' ] => true, } suitability.each do |arguments, expected| range, vstring = arguments actual = SemVer[range] === SemVer.new(vstring) actual.should == expected end end end describe 'instantiation' do it 'should raise an exception when passed an invalid version string' do expect { SemVer.new('invalidVersion') }.to raise_exception ArgumentError end it 'should populate the appropriate fields for a basic version string' do version = SemVer.new('1.2.3') version.major.should == 1 version.minor.should == 2 version.tiny.should == 3 version.special.should == '' end it 'should populate the appropriate fields for a special version string' do version = SemVer.new('3.4.5-beta6') version.major.should == 3 version.minor.should == 4 version.tiny.should == 5 version.special.should == '-beta6' end end describe '#matched_by?' do subject { SemVer.new('v1.2.3-beta') } describe 'should match against' do describe 'literal version strings' do it { should be_matched_by('1.2.3-beta') } it { should_not be_matched_by('1.2.3-alpha') } it { should_not be_matched_by('1.2.4-beta') } it { should_not be_matched_by('1.3.3-beta') } it { should_not be_matched_by('2.2.3-beta') } end describe 'partial version strings' do it { should be_matched_by('1.2.3') } it { should be_matched_by('1.2') } it { should be_matched_by('1') } end describe 'version strings with placeholders' do it { should be_matched_by('1.2.x') } it { should be_matched_by('1.x.3') } it { should be_matched_by('1.x.x') } it { should be_matched_by('1.x') } end end end describe 'comparisons' do describe 'against a string' do it 'should just work' do SemVer.new('1.2.3').should == '1.2.3' end end describe 'against a symbol' do it 'should just work' do SemVer.new('1.2.3').should == :'1.2.3' end end describe 'on a basic version (v1.2.3)' do subject { SemVer.new('v1.2.3') } it { should == SemVer.new('1.2.3') } # Different major versions it { should > SemVer.new('0.2.3') } it { should < SemVer.new('2.2.3') } # Different minor versions it { should > SemVer.new('1.1.3') } it { should < SemVer.new('1.3.3') } # Different tiny versions it { should > SemVer.new('1.2.2') } it { should < SemVer.new('1.2.4') } # Against special versions it { should > SemVer.new('1.2.3-beta') } it { should < SemVer.new('1.2.4-beta') } end describe 'on a special version (v1.2.3-beta)' do subject { SemVer.new('v1.2.3-beta') } it { should == SemVer.new('1.2.3-beta') } # Same version, final release it { should < SemVer.new('1.2.3') } # Different major versions it { should > SemVer.new('0.2.3') } it { should < SemVer.new('2.2.3') } # Different minor versions it { should > SemVer.new('1.1.3') } it { should < SemVer.new('1.3.3') } # Different tiny versions it { should > SemVer.new('1.2.2') } it { should < SemVer.new('1.2.4') } # Against special versions it { should > SemVer.new('1.2.3-alpha') } it { should < SemVer.new('1.2.3-beta2') } end end end diff --git a/spec/unit/settings/file_setting_spec.rb b/spec/unit/settings/file_setting_spec.rb index c77cc5981..7cce1b617 100755 --- a/spec/unit/settings/file_setting_spec.rb +++ b/spec/unit/settings/file_setting_spec.rb @@ -1,298 +1,298 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/settings' require 'puppet/settings/file_setting' describe Puppet::Settings::FileSetting do FileSetting = Puppet::Settings::FileSetting include PuppetSpec::Files describe "when controlling permissions" do def settings(wanted_values = {}) real_values = { :user => 'root', :group => 'root', :mkusers => false, :service_user_available? => false, :service_group_available? => false }.merge(wanted_values) settings = mock("settings") settings.stubs(:[]).with(:user).returns real_values[:user] settings.stubs(:[]).with(:group).returns real_values[:group] settings.stubs(:[]).with(:mkusers).returns real_values[:mkusers] settings.stubs(:service_user_available?).returns real_values[:service_user_available?] settings.stubs(:service_group_available?).returns real_values[:service_group_available?] settings end context "owner" do it "can always be root" do settings = settings(:user => "the_service", :mkusers => true) setting = FileSetting.new(:settings => settings, :owner => "root", :desc => "a setting") setting.owner.should == "root" end it "is the service user if we are making users" do settings = settings(:user => "the_service", :mkusers => true, :service_user_available? => false) setting = FileSetting.new(:settings => settings, :owner => "service", :desc => "a setting") setting.owner.should == "the_service" end it "is the service user if the user is available on the system" do settings = settings(:user => "the_service", :mkusers => false, :service_user_available? => true) setting = FileSetting.new(:settings => settings, :owner => "service", :desc => "a setting") setting.owner.should == "the_service" end it "is root when the setting specifies service and the user is not available on the system" do settings = settings(:user => "the_service", :mkusers => false, :service_user_available? => false) setting = FileSetting.new(:settings => settings, :owner => "service", :desc => "a setting") setting.owner.should == "root" end it "is unspecified when no specific owner is wanted" do FileSetting.new(:settings => settings(), :desc => "a setting").owner.should be_nil end it "does not allow other owners" do expect { FileSetting.new(:settings => settings(), :desc => "a setting", :name => "testing", :default => "the default", :owner => "invalid") }. to raise_error(FileSetting::SettingError, /The :owner parameter for the setting 'testing' must be either 'root' or 'service'/) end end context "group" do it "is unspecified when no specific group is wanted" do setting = FileSetting.new(:settings => settings(), :desc => "a setting") setting.group.should be_nil end it "is root if root is requested" do settings = settings(:group => "the_group") setting = FileSetting.new(:settings => settings, :group => "root", :desc => "a setting") setting.group.should == "root" end it "is the service group if we are making users" do settings = settings(:group => "the_service", :mkusers => true) setting = FileSetting.new(:settings => settings, :group => "service", :desc => "a setting") setting.group.should == "the_service" end it "is the service user if the group is available on the system" do settings = settings(:group => "the_service", :mkusers => false, :service_group_available? => true) setting = FileSetting.new(:settings => settings, :group => "service", :desc => "a setting") setting.group.should == "the_service" end it "is unspecified when the setting specifies service and the group is not available on the system" do settings = settings(:group => "the_service", :mkusers => false, :service_group_available? => false) setting = FileSetting.new(:settings => settings, :group => "service", :desc => "a setting") setting.group.should be_nil end it "does not allow other groups" do expect { FileSetting.new(:settings => settings(), :group => "invalid", :name => 'testing', :desc => "a setting") }. to raise_error(FileSetting::SettingError, /The :group parameter for the setting 'testing' must be either 'root' or 'service'/) end end end it "should be able to be converted into a resource" do FileSetting.new(:settings => mock("settings"), :desc => "eh").should respond_to(:to_resource) end describe "when being converted to a resource" do before do @basepath = make_absolute("/somepath") @settings = mock 'settings' @file = Puppet::Settings::FileSetting.new(:settings => @settings, :desc => "eh", :name => :myfile, :section => "mysect") @file.stubs(:create_files?).returns true @settings.stubs(:value).with(:myfile, nil, false).returns @basepath end it "should return :file as its type" do @file.type.should == :file end it "should skip non-existent files if 'create_files' is not enabled" do @file.expects(:create_files?).returns false @file.expects(:type).returns :file Puppet::FileSystem.expects(:exist?).with(@basepath).returns false @file.to_resource.should be_nil end it "should manage existent files even if 'create_files' is not enabled" do @file.expects(:create_files?).returns false @file.expects(:type).returns :file Puppet::FileSystem.stubs(:exist?) Puppet::FileSystem.expects(:exist?).with(@basepath).returns true @file.to_resource.should be_instance_of(Puppet::Resource) end describe "on POSIX systems", :if => Puppet.features.posix? do it "should skip files in /dev" do @settings.stubs(:value).with(:myfile, nil, false).returns "/dev/file" @file.to_resource.should be_nil end end it "should skip files whose paths are not strings" do @settings.stubs(:value).with(:myfile, nil, false).returns :foo @file.to_resource.should be_nil end it "should return a file resource with the path set appropriately" do resource = @file.to_resource resource.type.should == "File" resource.title.should == @basepath end it "should fully qualified returned files if necessary (#795)" do @settings.stubs(:value).with(:myfile, nil, false).returns "myfile" path = File.expand_path('myfile') @file.to_resource.title.should == path end it "should set the mode on the file if a mode is provided as an octal number" do @file.mode = 0755 @file.to_resource[:mode].should == '755' end it "should set the mode on the file if a mode is provided as a string" do @file.mode = '0755' @file.to_resource[:mode].should == '755' end it "should not set the mode on a the file if manage_internal_file_permissions is disabled" do Puppet[:manage_internal_file_permissions] = false @file.stubs(:mode).returns(0755) @file.to_resource[:mode].should == nil end it "should set the owner if running as root and the owner is provided" do Puppet.features.expects(:root?).returns true Puppet.features.stubs(:microsoft_windows?).returns false @file.stubs(:owner).returns "foo" @file.to_resource[:owner].should == "foo" end it "should not set the owner if manage_internal_file_permissions is disabled" do Puppet[:manage_internal_file_permissions] = false Puppet.features.stubs(:root?).returns true @file.stubs(:owner).returns "foo" @file.to_resource[:owner].should == nil end it "should set the group if running as root and the group is provided" do Puppet.features.expects(:root?).returns true Puppet.features.stubs(:microsoft_windows?).returns false @file.stubs(:group).returns "foo" @file.to_resource[:group].should == "foo" end it "should not set the group if manage_internal_file_permissions is disabled" do Puppet[:manage_internal_file_permissions] = false Puppet.features.stubs(:root?).returns true @file.stubs(:group).returns "foo" @file.to_resource[:group].should == nil end it "should not set owner if not running as root" do Puppet.features.expects(:root?).returns false Puppet.features.stubs(:microsoft_windows?).returns false @file.stubs(:owner).returns "foo" @file.to_resource[:owner].should be_nil end it "should not set group if not running as root" do Puppet.features.expects(:root?).returns false Puppet.features.stubs(:microsoft_windows?).returns false @file.stubs(:group).returns "foo" @file.to_resource[:group].should be_nil end describe "on Microsoft Windows systems" do before :each do Puppet.features.stubs(:microsoft_windows?).returns true end it "should not set owner" do @file.stubs(:owner).returns "foo" @file.to_resource[:owner].should be_nil end it "should not set group" do @file.stubs(:group).returns "foo" @file.to_resource[:group].should be_nil end end it "should set :ensure to the file type" do @file.expects(:type).returns :directory @file.to_resource[:ensure].should == :directory end it "should set the loglevel to :debug" do @file.to_resource[:loglevel].should == :debug end it "should set the backup to false" do - @file.to_resource[:backup].should be_false + @file.to_resource[:backup].should be_falsey end it "should tag the resource with the settings section" do @file.expects(:section).returns "mysect" @file.to_resource.should be_tagged("mysect") end it "should tag the resource with the setting name" do @file.to_resource.should be_tagged("myfile") end it "should tag the resource with 'settings'" do @file.to_resource.should be_tagged("settings") end it "should set links to 'follow'" do @file.to_resource[:links].should == :follow end end describe "#munge" do it 'does not expand the path of the special value :memory: so we can set dblocation to an in-memory database' do filesetting = FileSetting.new(:settings => mock("settings"), :desc => "eh") filesetting.munge(':memory:').should == ':memory:' end end end diff --git a/spec/unit/settings_spec.rb b/spec/unit/settings_spec.rb index 95d1ceb7a..a26bb862f 100755 --- a/spec/unit/settings_spec.rb +++ b/spec/unit/settings_spec.rb @@ -1,1750 +1,1750 @@ #! /usr/bin/env ruby require 'spec_helper' require 'ostruct' require 'puppet/settings/errors' require 'puppet_spec/files' require 'matchers/resource' describe Puppet::Settings do include PuppetSpec::Files include Matchers::Resource let(:main_config_file_default_location) do File.join(Puppet::Util::RunMode[:master].conf_dir, "puppet.conf") end let(:user_config_file_default_location) do File.join(Puppet::Util::RunMode[:user].conf_dir, "puppet.conf") end # Return a given object's file metadata. def metadata(setting) if setting.is_a?(Puppet::Settings::FileSetting) { :owner => setting.owner, :group => setting.group, :mode => setting.mode }.delete_if { |key, value| value.nil? } else nil end end describe "when specifying defaults" do before do @settings = Puppet::Settings.new end it "should start with no defined sections or parameters" do # Note this relies on undocumented side effect that eachsection returns the Settings internal # configuration on which keys returns all parameters. @settings.eachsection.keys.length.should == 0 end it "should not allow specification of default values associated with a section as an array" do expect { @settings.define_settings(:section, :myvalue => ["defaultval", "my description"]) }.to raise_error end it "should not allow duplicate parameter specifications" do @settings.define_settings(:section, :myvalue => { :default => "a", :desc => "b" }) lambda { @settings.define_settings(:section, :myvalue => { :default => "c", :desc => "d" }) }.should raise_error(ArgumentError) end it "should allow specification of default values associated with a section as a hash" do @settings.define_settings(:section, :myvalue => {:default => "defaultval", :desc => "my description"}) end it "should consider defined parameters to be valid" do @settings.define_settings(:section, :myvalue => { :default => "defaultval", :desc => "my description" }) - @settings.valid?(:myvalue).should be_true + @settings.valid?(:myvalue).should be_truthy end it "should require a description when defaults are specified with a hash" do lambda { @settings.define_settings(:section, :myvalue => {:default => "a value"}) }.should raise_error(ArgumentError) end it "should support specifying owner, group, and mode when specifying files" do @settings.define_settings(:section, :myvalue => {:type => :file, :default => "/some/file", :owner => "service", :mode => "boo", :group => "service", :desc => "whatever"}) end it "should support specifying a short name" do @settings.define_settings(:section, :myvalue => {:default => "w", :desc => "b", :short => "m"}) end it "should support specifying the setting type" do @settings.define_settings(:section, :myvalue => {:default => "/w", :desc => "b", :type => :string}) @settings.setting(:myvalue).should be_instance_of(Puppet::Settings::StringSetting) end it "should fail if an invalid setting type is specified" do lambda { @settings.define_settings(:section, :myvalue => {:default => "w", :desc => "b", :type => :foo}) }.should raise_error(ArgumentError) end it "should fail when short names conflict" do @settings.define_settings(:section, :myvalue => {:default => "w", :desc => "b", :short => "m"}) lambda { @settings.define_settings(:section, :myvalue => {:default => "w", :desc => "b", :short => "m"}) }.should raise_error(ArgumentError) end end describe "when initializing application defaults do" do let(:default_values) do values = {} PuppetSpec::Settings::TEST_APP_DEFAULT_DEFINITIONS.keys.each do |key| values[key] = 'default value' end values end before do @settings = Puppet::Settings.new @settings.define_settings(:main, PuppetSpec::Settings::TEST_APP_DEFAULT_DEFINITIONS) end it "should fail if the app defaults hash is missing any required values" do incomplete_default_values = default_values.reject { |key, _| key == :confdir } expect { @settings.initialize_app_defaults(default_values.reject { |key, _| key == :confdir }) }.to raise_error(Puppet::Settings::SettingsError) end # ultimately I'd like to stop treating "run_mode" as a normal setting, because it has so many special # case behaviors / uses. However, until that time... we need to make sure that our private run_mode= # setter method gets properly called during app initialization. it "sets the preferred run mode when initializing the app defaults" do @settings.initialize_app_defaults(default_values.merge(:run_mode => :master)) @settings.preferred_run_mode.should == :master end end describe "#call_hooks_deferred_to_application_initialization" do let(:good_default) { "yay" } let(:bad_default) { "$doesntexist" } before(:each) do @settings = Puppet::Settings.new end describe "when ignoring dependency interpolation errors" do let(:options) { {:ignore_interpolation_dependency_errors => true} } describe "if interpolation error" do it "should not raise an error" do hook_values = [] @settings.define_settings(:section, :badhook => {:default => bad_default, :desc => "boo", :call_hook => :on_initialize_and_write, :hook => lambda { |v| hook_values << v }}) expect do @settings.send(:call_hooks_deferred_to_application_initialization, options) end.to_not raise_error end end describe "if no interpolation error" do it "should not raise an error" do hook_values = [] @settings.define_settings(:section, :goodhook => {:default => good_default, :desc => "boo", :call_hook => :on_initialize_and_write, :hook => lambda { |v| hook_values << v }}) expect do @settings.send(:call_hooks_deferred_to_application_initialization, options) end.to_not raise_error end end end describe "when not ignoring dependency interpolation errors" do [ {}, {:ignore_interpolation_dependency_errors => false}].each do |options| describe "if interpolation error" do it "should raise an error" do hook_values = [] @settings.define_settings( :section, :badhook => { :default => bad_default, :desc => "boo", :call_hook => :on_initialize_and_write, :hook => lambda { |v| hook_values << v } } ) expect do @settings.send(:call_hooks_deferred_to_application_initialization, options) end.to raise_error(Puppet::Settings::InterpolationError) end it "should contain the setting name in error message" do hook_values = [] @settings.define_settings( :section, :badhook => { :default => bad_default, :desc => "boo", :call_hook => :on_initialize_and_write, :hook => lambda { |v| hook_values << v } } ) expect do @settings.send(:call_hooks_deferred_to_application_initialization, options) end.to raise_error(Puppet::Settings::InterpolationError, /badhook/) end end describe "if no interpolation error" do it "should not raise an error" do hook_values = [] @settings.define_settings( :section, :goodhook => { :default => good_default, :desc => "boo", :call_hook => :on_initialize_and_write, :hook => lambda { |v| hook_values << v } } ) expect do @settings.send(:call_hooks_deferred_to_application_initialization, options) end.to_not raise_error end end end end end describe "when setting values" do before do @settings = Puppet::Settings.new @settings.define_settings :main, :myval => { :default => "val", :desc => "desc" } @settings.define_settings :main, :bool => { :type => :boolean, :default => true, :desc => "desc" } end it "should provide a method for setting values from other objects" do @settings[:myval] = "something else" @settings[:myval].should == "something else" end it "should support a getopt-specific mechanism for setting values" do @settings.handlearg("--myval", "newval") @settings[:myval].should == "newval" end it "should support a getopt-specific mechanism for turning booleans off" do @settings.override_default(:bool, true) @settings.handlearg("--no-bool", "") @settings[:bool].should == false end it "should support a getopt-specific mechanism for turning booleans on" do # Turn it off first @settings.override_default(:bool, false) @settings.handlearg("--bool", "") @settings[:bool].should == true end it "should consider a cli setting with no argument to be a boolean" do # Turn it off first @settings.override_default(:bool, false) @settings.handlearg("--bool") @settings[:bool].should == true end it "should consider a cli setting with an empty string as an argument to be an empty argument, if the setting itself is not a boolean" do @settings.override_default(:myval, "bob") @settings.handlearg("--myval", "") @settings[:myval].should == "" end it "should consider a cli setting with a boolean as an argument to be a boolean" do # Turn it off first @settings.override_default(:bool, false) @settings.handlearg("--bool", "true") @settings[:bool].should == true end it "should not consider a cli setting of a non boolean with a boolean as an argument to be a boolean" do @settings.override_default(:myval, "bob") @settings.handlearg("--no-myval", "") @settings[:myval].should == "" end it "should flag string settings from the CLI" do @settings.handlearg("--myval", "12") - @settings.set_by_cli?(:myval).should be_true + @settings.set_by_cli?(:myval).should be_truthy end it "should flag bool settings from the CLI" do @settings.handlearg("--bool") - @settings.set_by_cli?(:bool).should be_true + @settings.set_by_cli?(:bool).should be_truthy end it "should not flag settings memory as from CLI" do @settings[:myval] = "12" - @settings.set_by_cli?(:myval).should be_false + @settings.set_by_cli?(:myval).should be_falsey end it "should clear the cache when setting getopt-specific values" do @settings.define_settings :mysection, :one => { :default => "whah", :desc => "yay" }, :two => { :default => "$one yay", :desc => "bah" } @settings.expects(:unsafe_flush_cache) @settings[:two].should == "whah yay" @settings.handlearg("--one", "else") @settings[:two].should == "else yay" end it "should clear the cache when the preferred_run_mode is changed" do @settings.expects(:flush_cache) @settings.preferred_run_mode = :master end it "should not clear other values when setting getopt-specific values" do @settings[:myval] = "yay" @settings.handlearg("--no-bool", "") @settings[:myval].should == "yay" end it "should clear the list of used sections" do @settings.expects(:clearused) @settings[:myval] = "yay" end describe "call_hook" do Puppet::Settings::StringSetting.available_call_hook_values.each do |val| describe "when :#{val}" do describe "and definition invalid" do it "should raise error if no hook defined" do expect do @settings.define_settings(:section, :hooker => {:default => "yay", :desc => "boo", :call_hook => val}) end.to raise_error(ArgumentError, /no :hook/) end it "should include the setting name in the error message" do expect do @settings.define_settings(:section, :hooker => {:default => "yay", :desc => "boo", :call_hook => val}) end.to raise_error(ArgumentError, /for :hooker/) end end describe "and definition valid" do before(:each) do hook_values = [] @settings.define_settings(:section, :hooker => {:default => "yay", :desc => "boo", :call_hook => val, :hook => lambda { |v| hook_values << v }}) end it "should call the hook when value written" do @settings.setting(:hooker).expects(:handle).with("something").once @settings[:hooker] = "something" end end end end it "should have a default value of :on_write_only" do @settings.define_settings(:section, :hooker => {:default => "yay", :desc => "boo", :hook => lambda { |v| hook_values << v }}) @settings.setting(:hooker).call_hook.should == :on_write_only end describe "when nil" do it "should generate a warning" do Puppet.expects(:warning) @settings.define_settings(:section, :hooker => {:default => "yay", :desc => "boo", :call_hook => nil, :hook => lambda { |v| hook_values << v }}) end it "should use default" do @settings.define_settings(:section, :hooker => {:default => "yay", :desc => "boo", :call_hook => nil, :hook => lambda { |v| hook_values << v }}) @settings.setting(:hooker).call_hook.should == :on_write_only end end describe "when invalid" do it "should raise an error" do expect do @settings.define_settings(:section, :hooker => {:default => "yay", :desc => "boo", :call_hook => :foo, :hook => lambda { |v| hook_values << v }}) end.to raise_error(ArgumentError, /invalid.*call_hook/i) end end describe "when :on_define_and_write" do it "should call the hook at definition" do hook_values = [] @settings.define_settings(:section, :hooker => {:default => "yay", :desc => "boo", :call_hook => :on_define_and_write, :hook => lambda { |v| hook_values << v }}) @settings.setting(:hooker).call_hook.should == :on_define_and_write hook_values.should == %w{yay} end end describe "when :on_initialize_and_write" do before(:each) do @hook_values = [] @settings.define_settings(:section, :hooker => {:default => "yay", :desc => "boo", :call_hook => :on_initialize_and_write, :hook => lambda { |v| @hook_values << v }}) end it "should not call the hook at definition" do @hook_values.should == [] @hook_values.should_not == %w{yay} end it "should call the hook at initialization" do app_defaults = {} Puppet::Settings::REQUIRED_APP_SETTINGS.each do |key| app_defaults[key] = "foo" end app_defaults[:run_mode] = :user @settings.define_settings(:main, PuppetSpec::Settings::TEST_APP_DEFAULT_DEFINITIONS) @settings.setting(:hooker).expects(:handle).with("yay").once @settings.initialize_app_defaults app_defaults end end end it "should call passed blocks when values are set" do values = [] @settings.define_settings(:section, :hooker => {:default => "yay", :desc => "boo", :hook => lambda { |v| values << v }}) values.should == [] @settings[:hooker] = "something" values.should == %w{something} end it "should call passed blocks when values are set via the command line" do values = [] @settings.define_settings(:section, :hooker => {:default => "yay", :desc => "boo", :hook => lambda { |v| values << v }}) values.should == [] @settings.handlearg("--hooker", "yay") values.should == %w{yay} end it "should provide an option to call passed blocks during definition" do values = [] @settings.define_settings(:section, :hooker => {:default => "yay", :desc => "boo", :call_hook => :on_define_and_write, :hook => lambda { |v| values << v }}) values.should == %w{yay} end it "should pass the fully interpolated value to the hook when called on definition" do values = [] @settings.define_settings(:section, :one => { :default => "test", :desc => "a" }) @settings.define_settings(:section, :hooker => {:default => "$one/yay", :desc => "boo", :call_hook => :on_define_and_write, :hook => lambda { |v| values << v }}) values.should == %w{test/yay} end it "should munge values using the setting-specific methods" do @settings[:bool] = "false" @settings[:bool].should == false end it "should prefer values set in ruby to values set on the cli" do @settings[:myval] = "memarg" @settings.handlearg("--myval", "cliarg") @settings[:myval].should == "memarg" end it "should raise an error if we try to set a setting that hasn't been defined'" do lambda{ @settings[:why_so_serious] = "foo" }.should raise_error(ArgumentError, /unknown setting/) end it "allows overriding cli args based on the cli-set value" do @settings.handlearg("--myval", "cliarg") @settings.patch_value(:myval, "modified #{@settings[:myval]}", :cli) expect(@settings[:myval]).to eq("modified cliarg") end end describe "when returning values" do before do @settings = Puppet::Settings.new @settings.define_settings :section, :config => { :type => :file, :default => "/my/file", :desc => "eh" }, :one => { :default => "ONE", :desc => "a" }, :two => { :default => "$one TWO", :desc => "b"}, :three => { :default => "$one $two THREE", :desc => "c"}, :four => { :default => "$two $three FOUR", :desc => "d"}, :five => { :default => nil, :desc => "e" } Puppet::FileSystem.stubs(:exist?).returns true end it "should provide a mechanism for returning set values" do @settings[:one] = "other" @settings[:one].should == "other" end it "setting a value to nil causes it to return to its default" do default_values = { :one => "skipped value" } [:logdir, :confdir, :vardir].each do |key| default_values[key] = 'default value' end @settings.define_settings :main, PuppetSpec::Settings::TEST_APP_DEFAULT_DEFINITIONS @settings.initialize_app_defaults(default_values) @settings[:one] = "value will disappear" @settings[:one] = nil @settings[:one].should == "ONE" end it "should interpolate default values for other parameters into returned parameter values" do @settings[:one].should == "ONE" @settings[:two].should == "ONE TWO" @settings[:three].should == "ONE ONE TWO THREE" end it "should interpolate default values that themselves need to be interpolated" do @settings[:four].should == "ONE TWO ONE ONE TWO THREE FOUR" end it "should provide a method for returning uninterpolated values" do @settings[:two] = "$one tw0" @settings.value(:two, nil, true).should == "$one tw0" @settings.value(:four, nil, true).should == "$two $three FOUR" end it "should interpolate set values for other parameters into returned parameter values" do @settings[:one] = "on3" @settings[:two] = "$one tw0" @settings[:three] = "$one $two thr33" @settings[:four] = "$one $two $three f0ur" @settings[:one].should == "on3" @settings[:two].should == "on3 tw0" @settings[:three].should == "on3 on3 tw0 thr33" @settings[:four].should == "on3 on3 tw0 on3 on3 tw0 thr33 f0ur" end it "should not cache interpolated values such that stale information is returned" do @settings[:two].should == "ONE TWO" @settings[:one] = "one" @settings[:two].should == "one TWO" end it "should have a run_mode that defaults to user" do @settings.preferred_run_mode.should == :user end it "interpolates a boolean false without raising an error" do @settings.define_settings(:section, :trip_wire => { :type => :boolean, :default => false, :desc => "a trip wire" }, :tripping => { :default => '$trip_wire', :desc => "once tripped if interpolated was false" }) @settings[:tripping].should == "false" end end describe "when choosing which value to return" do before do @settings = Puppet::Settings.new @settings.define_settings :section, :config => { :type => :file, :default => "/my/file", :desc => "a" }, :one => { :default => "ONE", :desc => "a" }, :two => { :default => "TWO", :desc => "b" } Puppet::FileSystem.stubs(:exist?).returns true @settings.preferred_run_mode = :agent end it "should return default values if no values have been set" do @settings[:one].should == "ONE" end it "should return values set on the cli before values set in the configuration file" do text = "[main]\none = fileval\n" @settings.stubs(:read_file).returns(text) @settings.handlearg("--one", "clival") @settings.send(:parse_config_files) @settings[:one].should == "clival" end it "should return values set in the mode-specific section before values set in the main section" do text = "[main]\none = mainval\n[agent]\none = modeval\n" @settings.stubs(:read_file).returns(text) @settings.send(:parse_config_files) @settings[:one].should == "modeval" end it "should not return values outside of its search path" do text = "[other]\none = oval\n" file = "/some/file" @settings.stubs(:read_file).returns(text) @settings.send(:parse_config_files) @settings[:one].should == "ONE" end it 'should use the current environment for $environment' do @settings.define_settings :main, :config_version => { :default => "$environment/foo", :desc => "mydocs" } @settings.value(:config_version, "myenv").should == "myenv/foo" end end describe "when locating config files" do before do @settings = Puppet::Settings.new end describe "when root" do it "should look for the main config file default location config settings haven't been overridden'" do Puppet.features.stubs(:root?).returns(true) Puppet::FileSystem.expects(:exist?).with(main_config_file_default_location).returns(false) Puppet::FileSystem.expects(:exist?).with(user_config_file_default_location).never @settings.send(:parse_config_files) end end describe "when not root" do it "should look for user config file default location if config settings haven't been overridden'" do Puppet.features.stubs(:root?).returns(false) seq = sequence "load config files" Puppet::FileSystem.expects(:exist?).with(user_config_file_default_location).returns(false).in_sequence(seq) @settings.send(:parse_config_files) end end end describe "when parsing its configuration" do before do @settings = Puppet::Settings.new @settings.stubs(:service_user_available?).returns true @settings.stubs(:service_group_available?).returns true @file = make_absolute("/some/file") @userconfig = make_absolute("/test/userconfigfile") @settings.define_settings :section, :user => { :default => "suser", :desc => "doc" }, :group => { :default => "sgroup", :desc => "doc" } @settings.define_settings :section, :config => { :type => :file, :default => @file, :desc => "eh" }, :one => { :default => "ONE", :desc => "a" }, :two => { :default => "$one TWO", :desc => "b" }, :three => { :default => "$one $two THREE", :desc => "c" } @settings.stubs(:user_config_file).returns(@userconfig) Puppet::FileSystem.stubs(:exist?).with(@file).returns true Puppet::FileSystem.stubs(:exist?).with(@userconfig).returns false end it "should not ignore the report setting" do @settings.define_settings :section, :report => { :default => "false", :desc => "a" } # This is needed in order to make sure we pass on windows myfile = File.expand_path(@file) @settings[:config] = myfile text = <<-CONF [puppetd] report=true CONF Puppet::FileSystem.expects(:exist?).with(myfile).returns(true) @settings.expects(:read_file).returns(text) @settings.send(:parse_config_files) - @settings[:report].should be_true + @settings[:report].should be_truthy end it "should use its current ':config' value for the file to parse" do myfile = make_absolute("/my/file") # do not stub expand_path here, as this leads to a stack overflow, when mocha tries to use it @settings[:config] = myfile Puppet::FileSystem.expects(:exist?).with(myfile).returns(true) Puppet::FileSystem.expects(:read).with(myfile).returns "[main]" @settings.send(:parse_config_files) end it "should not try to parse non-existent files" do Puppet::FileSystem.expects(:exist?).with(@file).returns false File.expects(:read).with(@file).never @settings.send(:parse_config_files) end it "should return values set in the configuration file" do text = "[main] one = fileval " @settings.expects(:read_file).returns(text) @settings.send(:parse_config_files) @settings[:one].should == "fileval" end #484 - this should probably be in the regression area it "should not throw an exception on unknown parameters" do text = "[main]\nnosuchparam = mval\n" @settings.expects(:read_file).returns(text) lambda { @settings.send(:parse_config_files) }.should_not raise_error end it "should convert booleans in the configuration file into Ruby booleans" do text = "[main] one = true two = false " @settings.expects(:read_file).returns(text) @settings.send(:parse_config_files) @settings[:one].should == true @settings[:two].should == false end it "should convert integers in the configuration file into Ruby Integers" do text = "[main] one = 65 " @settings.expects(:read_file).returns(text) @settings.send(:parse_config_files) @settings[:one].should == 65 end it "should support specifying all metadata (owner, group, mode) in the configuration file" do @settings.define_settings :section, :myfile => { :type => :file, :default => make_absolute("/myfile"), :desc => "a" } otherfile = make_absolute("/other/file") @settings.parse_config(<<-CONF) [main] myfile = #{otherfile} {owner = service, group = service, mode = 644} CONF @settings[:myfile].should == otherfile metadata(@settings.setting(:myfile)).should == {:owner => "suser", :group => "sgroup", :mode => "644"} end it "should support specifying a single piece of metadata (owner, group, or mode) in the configuration file" do @settings.define_settings :section, :myfile => { :type => :file, :default => make_absolute("/myfile"), :desc => "a" } otherfile = make_absolute("/other/file") @settings.parse_config(<<-CONF) [main] myfile = #{otherfile} {owner = service} CONF @settings[:myfile].should == otherfile metadata(@settings.setting(:myfile)).should == {:owner => "suser"} end it "should support loading metadata (owner, group, or mode) from a run_mode section in the configuration file" do default_values = {} PuppetSpec::Settings::TEST_APP_DEFAULT_DEFINITIONS.keys.each do |key| default_values[key] = 'default value' end @settings.define_settings :main, PuppetSpec::Settings::TEST_APP_DEFAULT_DEFINITIONS @settings.define_settings :master, :myfile => { :type => :file, :default => make_absolute("/myfile"), :desc => "a" } otherfile = make_absolute("/other/file") text = "[master] myfile = #{otherfile} {mode = 664} " @settings.expects(:read_file).returns(text) # will start initialization as user @settings.preferred_run_mode.should == :user @settings.send(:parse_config_files) # change app run_mode to master @settings.initialize_app_defaults(default_values.merge(:run_mode => :master)) @settings.preferred_run_mode.should == :master # initializing the app should have reloaded the metadata based on run_mode @settings[:myfile].should == otherfile metadata(@settings.setting(:myfile)).should == {:mode => "664"} end it "does not use the metadata from the same setting in a different section" do default_values = {} PuppetSpec::Settings::TEST_APP_DEFAULT_DEFINITIONS.keys.each do |key| default_values[key] = 'default value' end file = make_absolute("/file") default_mode = "0600" @settings.define_settings :main, PuppetSpec::Settings::TEST_APP_DEFAULT_DEFINITIONS @settings.define_settings :master, :myfile => { :type => :file, :default => file, :desc => "a", :mode => default_mode } text = "[master] myfile = #{file}/foo [agent] myfile = #{file} {mode = 664} " @settings.expects(:read_file).returns(text) # will start initialization as user @settings.preferred_run_mode.should == :user @settings.send(:parse_config_files) # change app run_mode to master @settings.initialize_app_defaults(default_values.merge(:run_mode => :master)) @settings.preferred_run_mode.should == :master # initializing the app should have reloaded the metadata based on run_mode @settings[:myfile].should == "#{file}/foo" metadata(@settings.setting(:myfile)).should == { :mode => default_mode } end it "should call hooks associated with values set in the configuration file" do values = [] @settings.define_settings :section, :mysetting => {:default => "defval", :desc => "a", :hook => proc { |v| values << v }} text = "[main] mysetting = setval " @settings.expects(:read_file).returns(text) @settings.send(:parse_config_files) values.should == ["setval"] end it "should not call the same hook for values set multiple times in the configuration file" do values = [] @settings.define_settings :section, :mysetting => {:default => "defval", :desc => "a", :hook => proc { |v| values << v }} text = "[user] mysetting = setval [main] mysetting = other " @settings.expects(:read_file).returns(text) @settings.send(:parse_config_files) values.should == ["setval"] end it "should pass the interpolated value to the hook when one is available" do values = [] @settings.define_settings :section, :base => {:default => "yay", :desc => "a", :hook => proc { |v| values << v }} @settings.define_settings :section, :mysetting => {:default => "defval", :desc => "a", :hook => proc { |v| values << v }} text = "[main] mysetting = $base/setval " @settings.expects(:read_file).returns(text) @settings.send(:parse_config_files) values.should == ["yay/setval"] end it "should allow hooks invoked at parse time to be deferred" do hook_invoked = false @settings.define_settings :section, :deferred => {:desc => '', :hook => proc { |v| hook_invoked = true }, :call_hook => :on_initialize_and_write, } @settings.define_settings(:main, :logdir => { :type => :directory, :default => nil, :desc => "logdir" }, :confdir => { :type => :directory, :default => nil, :desc => "confdir" }, :vardir => { :type => :directory, :default => nil, :desc => "vardir" }) text = <<-EOD [main] deferred=$confdir/goose EOD @settings.stubs(:read_file).returns(text) @settings.initialize_global_settings - hook_invoked.should be_false + hook_invoked.should be_falsey @settings.initialize_app_defaults(:logdir => '/path/to/logdir', :confdir => '/path/to/confdir', :vardir => '/path/to/vardir') - hook_invoked.should be_true + hook_invoked.should be_truthy @settings[:deferred].should eq(File.expand_path('/path/to/confdir/goose')) end it "does not require the value for a setting without a hook to resolve during global setup" do hook_invoked = false @settings.define_settings :section, :can_cause_problems => {:desc => '' } @settings.define_settings(:main, :logdir => { :type => :directory, :default => nil, :desc => "logdir" }, :confdir => { :type => :directory, :default => nil, :desc => "confdir" }, :vardir => { :type => :directory, :default => nil, :desc => "vardir" }) text = <<-EOD [main] can_cause_problems=$confdir/goose EOD @settings.stubs(:read_file).returns(text) @settings.initialize_global_settings @settings.initialize_app_defaults(:logdir => '/path/to/logdir', :confdir => '/path/to/confdir', :vardir => '/path/to/vardir') @settings[:can_cause_problems].should eq(File.expand_path('/path/to/confdir/goose')) end it "should allow empty values" do @settings.define_settings :section, :myarg => { :default => "myfile", :desc => "a" } text = "[main] myarg = " @settings.stubs(:read_file).returns(text) @settings.send(:parse_config_files) @settings[:myarg].should == "" end describe "deprecations" do let(:settings) { Puppet::Settings.new } let(:app_defaults) { { :logdir => "/dev/null", :confdir => "/dev/null", :vardir => "/dev/null", } } def assert_accessing_setting_is_deprecated(settings, setting) Puppet.expects(:deprecation_warning).with("Accessing '#{setting}' as a setting is deprecated.") Puppet.expects(:deprecation_warning).with("Modifying '#{setting}' as a setting is deprecated.") settings[setting.intern] = apath = File.expand_path('foo') expect(settings[setting.intern]).to eq(apath) end before(:each) do settings.define_settings(:main, { :logdir => { :default => 'a', :desc => 'a' }, :confdir => { :default => 'b', :desc => 'b' }, :vardir => { :default => 'c', :desc => 'c' }, }) end context "complete" do let(:completely_deprecated_settings) do settings.define_settings(:main, { :completely_deprecated_setting => { :default => 'foo', :desc => 'a deprecated setting', :deprecated => :completely, } }) settings end it "warns when set in puppet.conf" do Puppet.expects(:deprecation_warning).with(regexp_matches(/completely_deprecated_setting is deprecated\./), 'setting-completely_deprecated_setting') completely_deprecated_settings.parse_config(<<-CONF) completely_deprecated_setting='should warn' CONF completely_deprecated_settings.initialize_app_defaults(app_defaults) end it "warns when set on the commandline" do Puppet.expects(:deprecation_warning).with(regexp_matches(/completely_deprecated_setting is deprecated\./), 'setting-completely_deprecated_setting') args = ["--completely_deprecated_setting", "/some/value"] completely_deprecated_settings.send(:parse_global_options, args) completely_deprecated_settings.initialize_app_defaults(app_defaults) end it "warns when set in code" do assert_accessing_setting_is_deprecated(completely_deprecated_settings, 'completely_deprecated_setting') end end context "partial" do let(:partially_deprecated_settings) do settings.define_settings(:main, { :partially_deprecated_setting => { :default => 'foo', :desc => 'a partially deprecated setting', :deprecated => :allowed_on_commandline, } }) settings end it "warns for a deprecated setting allowed on the command line set in puppet.conf" do Puppet.expects(:deprecation_warning).with(regexp_matches(/partially_deprecated_setting is deprecated in puppet\.conf/), 'puppet-conf-setting-partially_deprecated_setting') partially_deprecated_settings.parse_config(<<-CONF) partially_deprecated_setting='should warn' CONF partially_deprecated_settings.initialize_app_defaults(app_defaults) end it "does not warn when manifest is set on command line" do Puppet.expects(:deprecation_warning).never args = ["--partially_deprecated_setting", "/some/value"] partially_deprecated_settings.send(:parse_global_options, args) partially_deprecated_settings.initialize_app_defaults(app_defaults) end it "warns when set in code" do assert_accessing_setting_is_deprecated(partially_deprecated_settings, 'partially_deprecated_setting') end end end end describe "when there are multiple config files" do let(:main_config_text) { "[main]\none = main\ntwo = main2" } let(:user_config_text) { "[main]\none = user\n" } let(:seq) { sequence "config_file_sequence" } before :each do @settings = Puppet::Settings.new @settings.define_settings(:section, { :confdir => { :default => nil, :desc => "Conf dir" }, :config => { :default => "$confdir/puppet.conf", :desc => "Config" }, :one => { :default => "ONE", :desc => "a" }, :two => { :default => "TWO", :desc => "b" }, }) end context "running non-root without explicit config file" do before :each do Puppet.features.stubs(:root?).returns(false) Puppet::FileSystem.expects(:exist?). with(user_config_file_default_location). returns(true).in_sequence(seq) @settings.expects(:read_file). with(user_config_file_default_location). returns(user_config_text).in_sequence(seq) end it "should return values from the user config file" do @settings.send(:parse_config_files) @settings[:one].should == "user" end it "should not return values from the main config file" do @settings.send(:parse_config_files) @settings[:two].should == "TWO" end end context "running as root without explicit config file" do before :each do Puppet.features.stubs(:root?).returns(true) Puppet::FileSystem.expects(:exist?). with(main_config_file_default_location). returns(true).in_sequence(seq) @settings.expects(:read_file). with(main_config_file_default_location). returns(main_config_text).in_sequence(seq) end it "should return values from the main config file" do @settings.send(:parse_config_files) @settings[:one].should == "main" end it "should not return values from the user config file" do @settings.send(:parse_config_files) @settings[:two].should == "main2" end end context "running with an explicit config file as a user (e.g. Apache + Passenger)" do before :each do Puppet.features.stubs(:root?).returns(false) @settings[:confdir] = File.dirname(main_config_file_default_location) Puppet::FileSystem.expects(:exist?). with(main_config_file_default_location). returns(true).in_sequence(seq) @settings.expects(:read_file). with(main_config_file_default_location). returns(main_config_text).in_sequence(seq) end it "should return values from the main config file" do @settings.send(:parse_config_files) @settings[:one].should == "main" end it "should not return values from the user config file" do @settings.send(:parse_config_files) @settings[:two].should == "main2" end end end describe "when reparsing its configuration" do before do @file = make_absolute("/test/file") @userconfig = make_absolute("/test/userconfigfile") @settings = Puppet::Settings.new @settings.define_settings :section, :config => { :type => :file, :default => @file, :desc => "a" }, :one => { :default => "ONE", :desc => "a" }, :two => { :default => "$one TWO", :desc => "b" }, :three => { :default => "$one $two THREE", :desc => "c" } Puppet::FileSystem.stubs(:exist?).with(@file).returns true Puppet::FileSystem.stubs(:exist?).with(@userconfig).returns false @settings.stubs(:user_config_file).returns(@userconfig) end it "does not create the WatchedFile instance and should not parse if the file does not exist" do Puppet::FileSystem.expects(:exist?).with(@file).returns false Puppet::Util::WatchedFile.expects(:new).never @settings.expects(:parse_config_files).never @settings.reparse_config_files end context "and watched file exists" do before do @watched_file = Puppet::Util::WatchedFile.new(@file) Puppet::Util::WatchedFile.expects(:new).with(@file).returns @watched_file end it "uses a WatchedFile instance to determine if the file has changed" do @watched_file.expects(:changed?) @settings.reparse_config_files end it "does not reparse if the file has not changed" do @watched_file.expects(:changed?).returns false @settings.expects(:parse_config_files).never @settings.reparse_config_files end it "reparses if the file has changed" do @watched_file.expects(:changed?).returns true @settings.expects(:parse_config_files) @settings.reparse_config_files end it "replaces in-memory values with on-file values" do @watched_file.stubs(:changed?).returns(true) @settings[:one] = "init" # Now replace the value text = "[main]\none = disk-replace\n" @settings.stubs(:read_file).returns(text) @settings.reparse_config_files @settings[:one].should == "disk-replace" end end it "should retain parameters set by cli when configuration files are reparsed" do @settings.handlearg("--one", "clival") text = "[main]\none = on-disk\n" @settings.stubs(:read_file).returns(text) @settings.send(:parse_config_files) @settings[:one].should == "clival" end it "should remove in-memory values that are no longer set in the file" do # Init the value text = "[main]\none = disk-init\n" @settings.expects(:read_file).returns(text) @settings.send(:parse_config_files) @settings[:one].should == "disk-init" # Now replace the value text = "[main]\ntwo = disk-replace\n" @settings.expects(:read_file).returns(text) @settings.send(:parse_config_files) # The originally-overridden value should be replaced with the default @settings[:one].should == "ONE" # and we should now have the new value in memory @settings[:two].should == "disk-replace" end it "should retain in-memory values if the file has a syntax error" do # Init the value text = "[main]\none = initial-value\n" @settings.expects(:read_file).with(@file).returns(text) @settings.send(:parse_config_files) @settings[:one].should == "initial-value" # Now replace the value with something bogus text = "[main]\nkenny = killed-by-what-follows\n1 is 2, blah blah florp\n" @settings.expects(:read_file).with(@file).returns(text) @settings.send(:parse_config_files) # The originally-overridden value should not be replaced with the default @settings[:one].should == "initial-value" # and we should not have the new value in memory @settings[:kenny].should be_nil end end it "should provide a method for creating a catalog of resources from its configuration" do Puppet::Settings.new.should respond_to(:to_catalog) end describe "when creating a catalog" do before do @settings = Puppet::Settings.new @settings.stubs(:service_user_available?).returns true @prefix = Puppet.features.posix? ? "" : "C:" end it "should add all file resources to the catalog if no sections have been specified" do @settings.define_settings :main, :maindir => { :type => :directory, :default => @prefix+"/maindir", :desc => "a"}, :seconddir => { :type => :directory, :default => @prefix+"/seconddir", :desc => "a"} @settings.define_settings :other, :otherdir => { :type => :directory, :default => @prefix+"/otherdir", :desc => "a" } catalog = @settings.to_catalog [@prefix+"/maindir", @prefix+"/seconddir", @prefix+"/otherdir"].each do |path| catalog.resource(:file, path).should be_instance_of(Puppet::Resource) end end it "should add only files in the specified sections if section names are provided" do @settings.define_settings :main, :maindir => { :type => :directory, :default => @prefix+"/maindir", :desc => "a" } @settings.define_settings :other, :otherdir => { :type => :directory, :default => @prefix+"/otherdir", :desc => "a" } catalog = @settings.to_catalog(:main) catalog.resource(:file, @prefix+"/otherdir").should be_nil catalog.resource(:file, @prefix+"/maindir").should be_instance_of(Puppet::Resource) end it "should not try to add the same file twice" do @settings.define_settings :main, :maindir => { :type => :directory, :default => @prefix+"/maindir", :desc => "a" } @settings.define_settings :other, :otherdir => { :type => :directory, :default => @prefix+"/maindir", :desc => "a" } lambda { @settings.to_catalog }.should_not raise_error end it "should ignore files whose :to_resource method returns nil" do @settings.define_settings :main, :maindir => { :type => :directory, :default => @prefix+"/maindir", :desc => "a" } @settings.setting(:maindir).expects(:to_resource).returns nil Puppet::Resource::Catalog.any_instance.expects(:add_resource).never @settings.to_catalog end describe "on Microsoft Windows" do before :each do Puppet.features.stubs(:root?).returns true Puppet.features.stubs(:microsoft_windows?).returns true @settings.define_settings :foo, :mkusers => { :type => :boolean, :default => true, :desc => "e" }, :user => { :default => "suser", :desc => "doc" }, :group => { :default => "sgroup", :desc => "doc" } @settings.define_settings :other, :otherdir => { :type => :directory, :default => "/otherdir", :desc => "a", :owner => "service", :group => "service"} @catalog = @settings.to_catalog end it "it should not add users and groups to the catalog" do @catalog.resource(:user, "suser").should be_nil @catalog.resource(:group, "sgroup").should be_nil end end describe "adding default directory environment to the catalog" do let(:tmpenv) { tmpdir("envs") } let(:default_path) { "#{tmpenv}/environments" } before(:each) do @settings.define_settings :main, :environment => { :default => "production", :desc => "env"}, :environmentpath => { :type => :path, :default => default_path, :desc => "envpath"} end it "adds if environmentpath exists" do envpath = "#{tmpenv}/custom_envpath" @settings[:environmentpath] = envpath Dir.mkdir(envpath) catalog = @settings.to_catalog expect(catalog.resource_keys).to include(["File", "#{envpath}/production"]) end it "adds the first directory of environmentpath" do envdir = "#{tmpenv}/custom_envpath" envpath = "#{envdir}#{File::PATH_SEPARATOR}/some/other/envdir" @settings[:environmentpath] = envpath Dir.mkdir(envdir) catalog = @settings.to_catalog expect(catalog.resource_keys).to include(["File", "#{envdir}/production"]) end it "handles a non-existent environmentpath" do catalog = @settings.to_catalog expect(catalog.resource_keys).to be_empty end it "handles a default environmentpath" do Dir.mkdir(default_path) catalog = @settings.to_catalog expect(catalog.resource_keys).to include(["File", "#{default_path}/production"]) end it "does not add if the path to the default directory environment exists as a symlink", :if => Puppet.features.manages_symlinks? do Dir.mkdir(default_path) Puppet::FileSystem.symlink("#{tmpenv}/nowhere", File.join(default_path, 'production')) catalog = @settings.to_catalog expect(catalog.resource_keys).to_not include(["File", "#{default_path}/production"]) end end describe "when adding users and groups to the catalog" do before do Puppet.features.stubs(:root?).returns true Puppet.features.stubs(:microsoft_windows?).returns false @settings.define_settings :foo, :mkusers => { :type => :boolean, :default => true, :desc => "e" }, :user => { :default => "suser", :desc => "doc" }, :group => { :default => "sgroup", :desc => "doc" } @settings.define_settings :other, :otherdir => {:type => :directory, :default => "/otherdir", :desc => "a", :owner => "service", :group => "service"} @catalog = @settings.to_catalog end it "should add each specified user and group to the catalog if :mkusers is a valid setting, is enabled, and we're running as root" do @catalog.resource(:user, "suser").should be_instance_of(Puppet::Resource) @catalog.resource(:group, "sgroup").should be_instance_of(Puppet::Resource) end it "should only add users and groups to the catalog from specified sections" do @settings.define_settings :yay, :yaydir => { :type => :directory, :default => "/yaydir", :desc => "a", :owner => "service", :group => "service"} catalog = @settings.to_catalog(:other) catalog.resource(:user, "jane").should be_nil catalog.resource(:group, "billy").should be_nil end it "should not add users or groups to the catalog if :mkusers not running as root" do Puppet.features.stubs(:root?).returns false catalog = @settings.to_catalog catalog.resource(:user, "suser").should be_nil catalog.resource(:group, "sgroup").should be_nil end it "should not add users or groups to the catalog if :mkusers is not a valid setting" do Puppet.features.stubs(:root?).returns true settings = Puppet::Settings.new settings.define_settings :other, :otherdir => {:type => :directory, :default => "/otherdir", :desc => "a", :owner => "service", :group => "service"} catalog = settings.to_catalog catalog.resource(:user, "suser").should be_nil catalog.resource(:group, "sgroup").should be_nil end it "should not add users or groups to the catalog if :mkusers is a valid setting but is disabled" do @settings[:mkusers] = false catalog = @settings.to_catalog catalog.resource(:user, "suser").should be_nil catalog.resource(:group, "sgroup").should be_nil end it "should not try to add users or groups to the catalog twice" do @settings.define_settings :yay, :yaydir => {:type => :directory, :default => "/yaydir", :desc => "a", :owner => "service", :group => "service"} # This would fail if users/groups were added twice lambda { @settings.to_catalog }.should_not raise_error end it "should set :ensure to :present on each created user and group" do @catalog.resource(:user, "suser")[:ensure].should == :present @catalog.resource(:group, "sgroup")[:ensure].should == :present end it "should set each created user's :gid to the service group" do @settings.to_catalog.resource(:user, "suser")[:gid].should == "sgroup" end it "should not attempt to manage the root user" do Puppet.features.stubs(:root?).returns true @settings.define_settings :foo, :foodir => {:type => :directory, :default => "/foodir", :desc => "a", :owner => "root", :group => "service"} @settings.to_catalog.resource(:user, "root").should be_nil end end end it "should be able to be converted to a manifest" do Puppet::Settings.new.should respond_to(:to_manifest) end describe "when being converted to a manifest" do it "should produce a string with the code for each resource joined by two carriage returns" do @settings = Puppet::Settings.new @settings.define_settings :main, :maindir => { :type => :directory, :default => "/maindir", :desc => "a"}, :seconddir => { :type => :directory, :default => "/seconddir", :desc => "a"} main = stub 'main_resource', :ref => "File[/maindir]" main.expects(:to_manifest).returns "maindir" second = stub 'second_resource', :ref => "File[/seconddir]" second.expects(:to_manifest).returns "seconddir" @settings.setting(:maindir).expects(:to_resource).returns main @settings.setting(:seconddir).expects(:to_resource).returns second @settings.to_manifest.split("\n\n").sort.should == %w{maindir seconddir} end end describe "when using sections of the configuration to manage the local host" do before do @settings = Puppet::Settings.new @settings.stubs(:service_user_available?).returns true @settings.stubs(:service_group_available?).returns true @settings.define_settings :main, :noop => { :default => false, :desc => "", :type => :boolean } @settings.define_settings :main, :maindir => { :type => :directory, :default => make_absolute("/maindir"), :desc => "a" }, :seconddir => { :type => :directory, :default => make_absolute("/seconddir"), :desc => "a"} @settings.define_settings :main, :user => { :default => "suser", :desc => "doc" }, :group => { :default => "sgroup", :desc => "doc" } @settings.define_settings :other, :otherdir => {:type => :directory, :default => make_absolute("/otherdir"), :desc => "a", :owner => "service", :group => "service", :mode => '0755'} @settings.define_settings :third, :thirddir => { :type => :directory, :default => make_absolute("/thirddir"), :desc => "b"} @settings.define_settings :files, :myfile => {:type => :file, :default => make_absolute("/myfile"), :desc => "a", :mode => '0755'} end it "should create a catalog with the specified sections" do @settings.expects(:to_catalog).with(:main, :other).returns Puppet::Resource::Catalog.new("foo") @settings.use(:main, :other) end it "should canonicalize the sections" do @settings.expects(:to_catalog).with(:main, :other).returns Puppet::Resource::Catalog.new("foo") @settings.use("main", "other") end it "should ignore sections that have already been used" do @settings.expects(:to_catalog).with(:main).returns Puppet::Resource::Catalog.new("foo") @settings.use(:main) @settings.expects(:to_catalog).with(:other).returns Puppet::Resource::Catalog.new("foo") @settings.use(:main, :other) end it "should convert the created catalog to a RAL catalog" do @catalog = Puppet::Resource::Catalog.new("foo") @settings.expects(:to_catalog).with(:main).returns @catalog @catalog.expects(:to_ral).returns @catalog @settings.use(:main) end it "should specify that it is not managing a host catalog" do catalog = Puppet::Resource::Catalog.new("foo") catalog.expects(:apply) @settings.expects(:to_catalog).returns catalog catalog.stubs(:to_ral).returns catalog catalog.expects(:host_config=).with false @settings.use(:main) end it "should support a method for re-using all currently used sections" do @settings.expects(:to_catalog).with(:main, :third).times(2).returns Puppet::Resource::Catalog.new("foo") @settings.use(:main, :third) @settings.reuse end it "should fail with an appropriate message if any resources fail" do @catalog = Puppet::Resource::Catalog.new("foo") @catalog.stubs(:to_ral).returns @catalog @settings.expects(:to_catalog).returns @catalog @trans = mock("transaction") @catalog.expects(:apply).yields(@trans) @trans.expects(:any_failed?).returns(true) resource = Puppet::Type.type(:notify).new(:title => 'failed') status = Puppet::Resource::Status.new(resource) event = Puppet::Transaction::Event.new( :name => 'failure', :status => 'failure', :message => 'My failure') status.add_event(event) report = Puppet::Transaction::Report.new('apply') report.add_resource_status(status) @trans.expects(:report).returns report @settings.expects(:raise).with(includes("My failure")) @settings.use(:whatever) end end describe "when dealing with printing configs" do before do @settings = Puppet::Settings.new #these are the magic default values @settings.stubs(:value).with(:configprint).returns("") @settings.stubs(:value).with(:genconfig).returns(false) @settings.stubs(:value).with(:genmanifest).returns(false) @settings.stubs(:value).with(:environment).returns(nil) end describe "when checking print_config?" do it "should return false when the :configprint, :genconfig and :genmanifest are not set" do - @settings.print_configs?.should be_false + @settings.print_configs?.should be_falsey end it "should return true when :configprint has a value" do @settings.stubs(:value).with(:configprint).returns("something") - @settings.print_configs?.should be_true + @settings.print_configs?.should be_truthy end it "should return true when :genconfig has a value" do @settings.stubs(:value).with(:genconfig).returns(true) - @settings.print_configs?.should be_true + @settings.print_configs?.should be_truthy end it "should return true when :genmanifest has a value" do @settings.stubs(:value).with(:genmanifest).returns(true) - @settings.print_configs?.should be_true + @settings.print_configs?.should be_truthy end end describe "when printing configs" do describe "when :configprint has a value" do it "should call print_config_options" do @settings.stubs(:value).with(:configprint).returns("something") @settings.expects(:print_config_options) @settings.print_configs end it "should get the value of the option using the environment" do @settings.stubs(:value).with(:configprint).returns("something") @settings.stubs(:include?).with("something").returns(true) @settings.expects(:value).with(:environment).returns("env") @settings.expects(:value).with("something", "env").returns("foo") @settings.stubs(:puts).with("foo") @settings.print_configs end it "should print the value of the option" do @settings.stubs(:value).with(:configprint).returns("something") @settings.stubs(:include?).with("something").returns(true) @settings.stubs(:value).with("something", nil).returns("foo") @settings.expects(:puts).with("foo") @settings.print_configs end it "should print the value pairs if there are multiple options" do @settings.stubs(:value).with(:configprint).returns("bar,baz") @settings.stubs(:include?).with("bar").returns(true) @settings.stubs(:include?).with("baz").returns(true) @settings.stubs(:value).with("bar", nil).returns("foo") @settings.stubs(:value).with("baz", nil).returns("fud") @settings.expects(:puts).with("bar = foo") @settings.expects(:puts).with("baz = fud") @settings.print_configs end it "should return true after printing" do @settings.stubs(:value).with(:configprint).returns("something") @settings.stubs(:include?).with("something").returns(true) @settings.stubs(:value).with("something", nil).returns("foo") @settings.stubs(:puts).with("foo") - @settings.print_configs.should be_true + @settings.print_configs.should be_truthy end it "should return false if a config param is not found" do @settings.stubs :puts @settings.stubs(:value).with(:configprint).returns("something") @settings.stubs(:include?).with("something").returns(false) - @settings.print_configs.should be_false + @settings.print_configs.should be_falsey end end describe "when genconfig is true" do before do @settings.stubs :puts end it "should call to_config" do @settings.stubs(:value).with(:genconfig).returns(true) @settings.expects(:to_config) @settings.print_configs end it "should return true from print_configs" do @settings.stubs(:value).with(:genconfig).returns(true) @settings.stubs(:to_config) - @settings.print_configs.should be_true + @settings.print_configs.should be_truthy end end describe "when genmanifest is true" do before do @settings.stubs :puts end it "should call to_config" do @settings.stubs(:value).with(:genmanifest).returns(true) @settings.expects(:to_manifest) @settings.print_configs end it "should return true from print_configs" do @settings.stubs(:value).with(:genmanifest).returns(true) @settings.stubs(:to_manifest) - @settings.print_configs.should be_true + @settings.print_configs.should be_truthy end end end end describe "when determining if the service user is available" do let(:settings) do settings = Puppet::Settings.new settings.define_settings :main, :user => { :default => nil, :desc => "doc" } settings end def a_user_type_for(username) user = mock 'user' Puppet::Type.type(:user).expects(:new).with { |args| args[:name] == username }.returns user user end it "should return false if there is no user setting" do settings.should_not be_service_user_available end it "should return false if the user provider says the user is missing" do settings[:user] = "foo" a_user_type_for("foo").expects(:exists?).returns false settings.should_not be_service_user_available end it "should return true if the user provider says the user is present" do settings[:user] = "foo" a_user_type_for("foo").expects(:exists?).returns true settings.should be_service_user_available end it "caches the result of determining if the user is present" do settings[:user] = "foo" a_user_type_for("foo").expects(:exists?).returns true settings.should be_service_user_available settings.should be_service_user_available end end describe "when determining if the service group is available" do let(:settings) do settings = Puppet::Settings.new settings.define_settings :main, :group => { :default => nil, :desc => "doc" } settings end def a_group_type_for(groupname) group = mock 'group' Puppet::Type.type(:group).expects(:new).with { |args| args[:name] == groupname }.returns group group end it "should return false if there is no group setting" do settings.should_not be_service_group_available end it "should return false if the group provider says the group is missing" do settings[:group] = "foo" a_group_type_for("foo").expects(:exists?).returns false settings.should_not be_service_group_available end it "should return true if the group provider says the group is present" do settings[:group] = "foo" a_group_type_for("foo").expects(:exists?).returns true settings.should be_service_group_available end it "caches the result of determining if the group is present" do settings[:group] = "foo" a_group_type_for("foo").expects(:exists?).returns true settings.should be_service_group_available settings.should be_service_group_available end end describe "when dealing with command-line options" do let(:settings) { Puppet::Settings.new } it "should get options from Puppet.settings.optparse_addargs" do settings.expects(:optparse_addargs).returns([]) settings.send(:parse_global_options, []) end it "should add options to OptionParser" do settings.stubs(:optparse_addargs).returns( [["--option","-o", "Funny Option", :NONE]]) settings.expects(:handlearg).with("--option", true) settings.send(:parse_global_options, ["--option"]) end it "should not die if it sees an unrecognized option, because the app/face may handle it later" do expect { settings.send(:parse_global_options, ["--topuppet", "value"]) } .to_not raise_error end it "should not pass an unrecognized option to handleargs" do settings.expects(:handlearg).with("--topuppet", "value").never expect { settings.send(:parse_global_options, ["--topuppet", "value"]) } .to_not raise_error end it "should pass valid puppet settings options to handlearg even if they appear after an unrecognized option" do settings.stubs(:optparse_addargs).returns( [["--option","-o", "Funny Option", :NONE]]) settings.expects(:handlearg).with("--option", true) settings.send(:parse_global_options, ["--invalidoption", "--option"]) end it "should transform boolean option to normal form" do Puppet::Settings.clean_opt("--[no-]option", true).should == ["--option", true] end it "should transform boolean option to no- form" do Puppet::Settings.clean_opt("--[no-]option", false).should == ["--no-option", false] end it "should set preferred run mode from --run_mode string without error" do args = ["--run_mode", "master"] settings.expects(:handlearg).with("--run_mode", "master").never expect { settings.send(:parse_global_options, args) } .to_not raise_error Puppet.settings.preferred_run_mode.should == :master args.empty?.should == true end it "should set preferred run mode from --run_mode= string without error" do args = ["--run_mode=master"] settings.expects(:handlearg).with("--run_mode", "master").never expect { settings.send(:parse_global_options, args) } .to_not raise_error Puppet.settings.preferred_run_mode.should == :master args.empty?.should == true end end describe "default_certname" do describe "using hostname and domainname" do before :each do Puppet::Settings.stubs(:hostname_fact).returns("testhostname") Puppet::Settings.stubs(:domain_fact).returns("domain.test.") end it "should use both to generate fqdn" do Puppet::Settings.default_certname.should =~ /testhostname\.domain\.test/ end it "should remove trailing dots from fqdn" do Puppet::Settings.default_certname.should == 'testhostname.domain.test' end end describe "using just hostname" do before :each do Puppet::Settings.stubs(:hostname_fact).returns("testhostname") Puppet::Settings.stubs(:domain_fact).returns("") end it "should use only hostname to generate fqdn" do Puppet::Settings.default_certname.should == "testhostname" end it "should removing trailing dots from fqdn" do Puppet::Settings.default_certname.should == "testhostname" end end end end diff --git a/spec/unit/ssl/certificate_authority_spec.rb b/spec/unit/ssl/certificate_authority_spec.rb index 2881b0a1e..79afa486e 100755 --- a/spec/unit/ssl/certificate_authority_spec.rb +++ b/spec/unit/ssl/certificate_authority_spec.rb @@ -1,1130 +1,1130 @@ #! /usr/bin/env ruby # encoding: ASCII-8BIT require 'spec_helper' require 'puppet/ssl/certificate_authority' describe Puppet::SSL::CertificateAuthority do after do Puppet::SSL::CertificateAuthority.instance_variable_set(:@singleton_instance, nil) 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[:ca] = 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[:ca] = 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::SSL::CertificateAuthority.any_instance.stubs(:setup) end it "should always set its name to the value of :certname" do Puppet[:certname] = "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 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[:cacrl] = "/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::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[:capass] = File.expand_path("/path/to/pass") Puppet::FileSystem.expects(:exist?).with(Puppet[:capass]).returns false fh = StringIO.new Puppet.settings.setting(:capass).expects(:open).with('w').yields fh @ca.stubs(:sign) @ca.generate_ca_certificate expect(fh.string.length).to be > 18 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 Puppet::SSL::Certificate.indirection.stubs(:save) # Stub out the factory Puppet::SSL::CertificateFactory.stubs(:build).returns @cert.content @request_content = stub "request content stub", :subject => OpenSSL::X509::Name.new([['CN', @name]]), :public_key => stub('public_key') @request = stub 'request', :name => @name, :request_extensions => [], :subject_alt_names => [], :content => @request_content @request_content.stubs(:verify).returns(true) # And the inventory @inventory = stub 'inventory', :add => nil @ca.stubs(:inventory).returns @inventory Puppet::SSL::CertificateRequest.indirection.stubs(:destroy) 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 @cert.content @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 @cert.content @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 @cert.content @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 @cert.content @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.not_to raise_error 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 @cert.content @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 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) }.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) }.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 @cert.content signed = @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 @cert.content @ca.sign(@name) end it "should sign the resulting certificate using its real key and a digest" do digest = mock 'digest' OpenSSL::Digest::SHA256.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 Puppet[:passfile] = '/f00' @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", "\xE2\x98\x83 :(" ].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 "accepts numeric OIDs under the ppRegCertExt subtree" do exts = [{ 'oid' => '1.3.6.1.4.1.34380.1.1.1', 'value' => '657e4780-4cf5-11e3-8f96-0800200c9a66'}] @request.stubs(:request_extensions).returns exts expect { @ca.check_internal_signing_policies(@name, @request, false) }.to_not raise_error end it "accepts short name OIDs under the ppRegCertExt subtree" do exts = [{ 'oid' => 'pp_uuid', 'value' => '657e4780-4cf5-11e3-8f96-0800200c9a66'}] @request.stubs(:request_extensions).returns exts expect { @ca.check_internal_signing_policies(@name, @request, false) }.to_not raise_error end it "accepts OIDs under the ppPrivCertAttrs subtree" do exts = [{ 'oid' => '1.3.6.1.4.1.34380.1.2.1', 'value' => 'private extension'}] @request.stubs(:request_extensions).returns exts expect { @ca.check_internal_signing_policies(@name, @request, false) }.to_not raise_error 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 let(:csr) { Puppet::SSL::CertificateRequest.new("host") } describe "using the autosign setting" do let(:autosign) { File.expand_path("/auto/sign") } it "should do nothing if autosign is disabled" do Puppet[:autosign] = false @ca.expects(:sign).never @ca.autosign(csr) end it "should do nothing if no autosign.conf exists" do Puppet[:autosign] = autosign non_existent_file = Puppet::FileSystem::MemoryFile.a_missing_file(autosign) Puppet::FileSystem.overlay(non_existent_file) do @ca.expects(:sign).never @ca.autosign(csr) end end describe "and autosign is enabled and the autosign.conf file exists" do let(:store) { stub 'store', :allow => nil, :allowed? => false } before do Puppet[:autosign] = autosign 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::FileSystem.overlay(Puppet::FileSystem::MemoryFile.a_regular_file_containing(autosign, "one\ntwo\n")) do Puppet::Network::AuthStore.stubs(:new).returns store store.expects(:allow).with("one") store.expects(:allow).with("two") @ca.autosign(csr) end end it "should reparse the autosign configuration on each call" do Puppet::FileSystem.overlay(Puppet::FileSystem::MemoryFile.a_regular_file_containing(autosign, "one")) do Puppet::Network::AuthStore.stubs(:new).times(2).returns store @ca.autosign(csr) @ca.autosign(csr) end end it "should ignore comments" do Puppet::FileSystem.overlay(Puppet::FileSystem::MemoryFile.a_regular_file_containing(autosign, "one\n#two\n")) do Puppet::Network::AuthStore.stubs(:new).returns store store.expects(:allow).with("one") @ca.autosign(csr) end end it "should ignore blank lines" do Puppet::FileSystem.overlay(Puppet::FileSystem::MemoryFile.a_regular_file_containing(autosign, "one\n\n")) do Puppet::Network::AuthStore.stubs(:new).returns store store.expects(:allow).with("one") @ca.autosign(csr) end end end end end describe "using the autosign command setting" do let(:cmd) { File.expand_path('/autosign_cmd') } let(:autosign_cmd) { mock 'autosign_command' } let(:autosign_executable) { Puppet::FileSystem::MemoryFile.an_executable(cmd) } before do Puppet[:autosign] = cmd Puppet::SSL::CertificateAuthority::AutosignCommand.stubs(:new).returns autosign_cmd end it "autosigns the CSR if the autosign command returned true" do Puppet::FileSystem.overlay(autosign_executable) do autosign_cmd.expects(:allowed?).with(csr).returns true @ca.expects(:sign).with('host') @ca.autosign(csr) end end it "doesn't autosign the CSR if the autosign_command returned false" do Puppet::FileSystem.overlay(autosign_executable) do autosign_cmd.expects(:allowed?).with(csr).returns false @ca.expects(:sign).never @ca.autosign(csr) end 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 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 it "should list the full certificates" do cert1 = stub 'cert1', :name => "cert1" cert2 = stub 'cert2', :name => "cert2" Puppet::SSL::Certificate.indirection.expects(:search).with("*").returns [cert1, cert2] @ca.list_certificates.should == [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 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 let(:cacert) { File.expand_path("/ca/cert") } before do @store = stub 'store', :verify => true, :add_file => nil, :purpose= => nil, :add_crl => true, :flags= => nil OpenSSL::X509::Store.stubs(:new).returns @store @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) 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[:cacert] = cacert @store.expects(:add_file).with cacert @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[:cacert] = cacert @store.expects(:add_file).with cacert @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 expect { @ca.verify("me") }.to raise_error end describe "certificate_is_alive?" do it "should return false if verification fails" do @cert.expects(:content).returns "mycert" @store.expects(:verify).with("mycert").returns false - @ca.certificate_is_alive?(@cert).should be_false + @ca.certificate_is_alive?(@cert).should be_falsey end it "should return true if verification passes" do @cert.expects(:content).returns "mycert" @store.expects(:verify).with("mycert").returns true - @ca.certificate_is_alive?(@cert).should be_true + @ca.certificate_is_alive?(@cert).should be_truthy end it "should used a cached instance of the x509 store" do OpenSSL::X509::Store.stubs(:new).returns(@store).once @cert.expects(:content).returns "mycert" @store.expects(:verify).with("mycert").returns true @ca.certificate_is_alive?(@cert) @ca.certificate_is_alive?(@cert) end 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 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(:serials).with("host").returns [16] @ca.crl.expects(:revoke).with { |serial, key| serial == 16 } @ca.revoke('host') end it "should revoke all serials matching a name" 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(:serials).with("host").returns [16, 20, 25] @ca.crl.expects(:revoke).with { |serial, key| serial == 16 } @ca.crl.expects(:revoke).with { |serial, key| serial == 20 } @ca.crl.expects(:revoke).with { |serial, key| serial == 25 } @ca.revoke('host') end it "should raise an error if no certificate match" 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(:serials).with("host").returns [] @ca.crl.expects(:revoke).never expect { @ca.revoke('host') }.to raise_error end context "revocation by serial number (#16798)" do it "revokes when given a lower case hexadecimal formatted string" do @ca.crl.expects(:revoke).with { |serial, key| serial == 15 } Puppet::SSL::Certificate.indirection.expects(:find).with("0xf").returns nil @ca.revoke('0xf') end it "revokes when given an upper case hexadecimal formatted string" do @ca.crl.expects(:revoke).with { |serial, key| serial == 15 } Puppet::SSL::Certificate.indirection.expects(:find).with("0xF").returns nil @ca.revoke('0xF') end it "handles very large serial numbers" do bighex = '0x4000000000000000000000000000000000000000' bighex_int = 365375409332725729550921208179070754913983135744 @ca.crl.expects(:revoke).with(bighex_int, anything) Puppet::SSL::Certificate.indirection.expects(:find).with(bighex).returns nil @ca.revoke(bighex) end end end it "should be able to generate a complete new SSL host" do @ca.should respond_to(:generate) end end end require 'puppet/indirector/memory' module CertificateAuthorityGenerateSpecs describe "CertificateAuthority.generate" do def expect_to_increment_serial_file Puppet.settings.setting(:serial).expects(:exclusive_open) end def expect_to_sign_a_cert expect_to_increment_serial_file end def expect_to_write_the_ca_password Puppet.settings.setting(:capass).expects(:open).with('w') end def expect_ca_initialization expect_to_write_the_ca_password expect_to_sign_a_cert end INDIRECTED_CLASSES = [ Puppet::SSL::Certificate, Puppet::SSL::CertificateRequest, Puppet::SSL::CertificateRevocationList, Puppet::SSL::Key, ] INDIRECTED_CLASSES.each do |const| class const::Memory < Puppet::Indirector::Memory # @return Array of all the indirector's values # # This mirrors Puppet::Indirector::SslFile#search which returns all files # in the directory. def search(request) return @instances.values end end end before do Puppet::SSL::Inventory.stubs(:new).returns(stub("Inventory", :add => nil)) INDIRECTED_CLASSES.each { |const| const.indirection.terminus_class = :memory } end after do INDIRECTED_CLASSES.each do |const| const.indirection.terminus_class = :file const.indirection.termini.clear end end describe "when generating certificates" do let(:ca) { Puppet::SSL::CertificateAuthority.new } before do expect_ca_initialization end it "should fail if a certificate already exists for the host" do cert = Puppet::SSL::Certificate.new('pre.existing') Puppet::SSL::Certificate.indirection.save(cert) expect { ca.generate(cert.name) }.to raise_error(ArgumentError, /a certificate already exists/i) end describe "that do not yet exist" do let(:cn) { "new.host" } def expect_cert_does_not_exist(cn) expect( Puppet::SSL::Certificate.indirection.find(cn) ).to be_nil end before do expect_to_sign_a_cert expect_cert_does_not_exist(cn) end it "should return the created certificate" do cert = ca.generate(cn) expect( cert ).to be_kind_of(Puppet::SSL::Certificate) expect( cert.name ).to eq(cn) end it "should not have any subject_alt_names by default" do cert = ca.generate(cn) expect( cert.subject_alt_names ).to be_empty end it "should have subject_alt_names if passed dns_alt_names" do cert = ca.generate(cn, :dns_alt_names => 'foo,bar') expect( cert.subject_alt_names ).to match_array(["DNS:#{cn}",'DNS:foo','DNS:bar']) end context "if autosign is false" do before do Puppet[:autosign] = false end it "should still generate and explicitly sign the request" do cert = nil cert = ca.generate(cn) expect(cert.name).to eq(cn) end end context "if autosign is true (Redmine #6112)" do def run_mode_must_be_master_for_autosign_to_be_attempted Puppet.stubs(:run_mode).returns(Puppet::Util::RunMode[:master]) end before do Puppet[:autosign] = true run_mode_must_be_master_for_autosign_to_be_attempted Puppet::Util::Log.level = :info end it "should generate a cert without attempting to sign again" do cert = ca.generate(cn) expect(cert.name).to eq(cn) expect(@logs.map(&:message)).to include("Autosigning #{cn}") end end end end end end diff --git a/spec/unit/ssl/certificate_request_attributes_spec.rb b/spec/unit/ssl/certificate_request_attributes_spec.rb index 5c8f93b1b..c1662c2ad 100644 --- a/spec/unit/ssl/certificate_request_attributes_spec.rb +++ b/spec/unit/ssl/certificate_request_attributes_spec.rb @@ -1,61 +1,61 @@ require 'spec_helper' require 'puppet/ssl/certificate_request_attributes' describe Puppet::SSL::CertificateRequestAttributes do let(:expected) do { "custom_attributes" => { "1.3.6.1.4.1.34380.2.2"=>[3232235521, 3232235777], # system IPs in hex "1.3.6.1.4.1.34380.2.0"=>"hostname.domain.com", } } end let(:csr_attributes_hash) { expected.dup } let(:csr_attributes_path) { '/some/where/csr_attributes.yaml' } let(:csr_attributes) { Puppet::SSL::CertificateRequestAttributes.new(csr_attributes_path) } it "initializes with a path" do expect(csr_attributes.path).to eq(csr_attributes_path) end describe "loading" do it "returns nil when loading from a non-existent file" do - expect(csr_attributes.load).to be_false + expect(csr_attributes.load).to be_falsey end context "with an available attributes file" do before do Puppet::FileSystem.expects(:exist?).with(csr_attributes_path).returns(true) Puppet::Util::Yaml.expects(:load_file).with(csr_attributes_path, {}).returns(csr_attributes_hash) end it "loads csr attributes from a file when the file is present" do - expect(csr_attributes.load).to be_true + expect(csr_attributes.load).to be_truthy end it "exposes custom_attributes" do csr_attributes.load expect(csr_attributes.custom_attributes).to eq(expected['custom_attributes']) end it "returns an empty hash if custom_attributes points to nil" do csr_attributes_hash["custom_attributes"] = nil csr_attributes.load expect(csr_attributes.custom_attributes).to eq({}) end it "returns an empty hash if custom_attributes key is not present" do csr_attributes_hash.delete("custom_attributes") csr_attributes.load expect(csr_attributes.custom_attributes).to eq({}) end it "raise a Puppet::Error if an unexpected root key is defined" do csr_attributes_hash['unintentional'] = 'data' expect { csr_attributes.load }.to raise_error(Puppet::Error, /unexpected attributes.*unintentional/) end end end end diff --git a/spec/unit/ssl/certificate_request_spec.rb b/spec/unit/ssl/certificate_request_spec.rb index ff65eef78..64922d465 100755 --- a/spec/unit/ssl/certificate_request_spec.rb +++ b/spec/unit/ssl/certificate_request_spec.rb @@ -1,381 +1,381 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/ssl/certificate_request' require 'puppet/ssl/key' describe Puppet::SSL::CertificateRequest do let(:request) { described_class.new("myname") } let(:key) { k = Puppet::SSL::Key.new("myname") k.generate k } it "should be extended with the Indirector module" do described_class.singleton_class.should be_include(Puppet::Indirector) end it "should indirect certificate_request" do described_class.indirection.name.should == :certificate_request end it "should use any provided name as its name" do described_class.new("myname").name.should == "myname" end it "should only support the text format" do described_class.supported_formats.should == [:s] end describe "when converting from a string" do it "should create a CSR instance with its name set to the CSR subject and its content set to the extracted CSR" do csr = stub 'csr', :subject => OpenSSL::X509::Name.parse("/CN=Foo.madstop.com"), :is_a? => true OpenSSL::X509::Request.expects(:new).with("my csr").returns(csr) mycsr = stub 'sslcsr' mycsr.expects(:content=).with(csr) described_class.expects(:new).with("Foo.madstop.com").returns mycsr described_class.from_s("my csr") end end describe "when managing instances" do it "should have a name attribute" do request.name.should == "myname" end it "should downcase its name" do described_class.new("MyName").name.should == "myname" end it "should have a content attribute" do request.should respond_to(:content) end it "should be able to read requests from disk" do path = "/my/path" File.expects(:read).with(path).returns("my request") my_req = mock 'request' OpenSSL::X509::Request.expects(:new).with("my request").returns(my_req) request.read(path).should equal(my_req) request.content.should equal(my_req) end it "should return an empty string when converted to a string with no request" do request.to_s.should == "" end it "should convert the request to pem format when converted to a string" do request.generate(key) request.to_s.should == request.content.to_pem end it "should have a :to_text method that it delegates to the actual key" do real_request = mock 'request' real_request.expects(:to_text).returns "requesttext" request.content = real_request request.to_text.should == "requesttext" end end describe "when generating" do it "should use the content of the provided key if the key is a Puppet::SSL::Key instance" do request.generate(key) - request.content.verify(key.content.public_key).should be_true + request.content.verify(key.content.public_key).should be_truthy end it "should set the subject to [CN, name]" do request.generate(key) # OpenSSL::X509::Name only implements equality as `eql?` request.content.subject.should eql OpenSSL::X509::Name.new([['CN', key.name]]) end it "should set the CN to the :ca_name setting when the CSR is for a CA" do Puppet[:ca_name] = "mycertname" request = described_class.new(Puppet::SSL::CA_NAME).generate(key) request.subject.should eql OpenSSL::X509::Name.new([['CN', Puppet[:ca_name]]]) end it "should set the version to 0" do request.generate(key) request.content.version.should == 0 end it "should set the public key to the provided key's public key" do request.generate(key) # The openssl bindings do not define equality on keys so we use to_s request.content.public_key.to_s.should == key.content.public_key.to_s end context "without subjectAltName / dns_alt_names" do before :each do Puppet[:dns_alt_names] = "" end ["extreq", "msExtReq"].each do |name| it "should not add any #{name} attribute" do request.generate(key) request.content.attributes.find do |attr| attr.oid == name end.should_not be end it "should return no subjectAltNames" do request.generate(key) request.subject_alt_names.should be_empty end end end context "with dns_alt_names" do before :each do Puppet[:dns_alt_names] = "one, two, three" end ["extreq", "msExtReq"].each do |name| it "should not add any #{name} attribute" do request.generate(key) request.content.attributes.find do |attr| attr.oid == name end.should_not be end it "should return no subjectAltNames" do request.generate(key) request.subject_alt_names.should be_empty end end end context "with subjectAltName to generate request" do before :each do Puppet[:dns_alt_names] = "" end it "should add an extreq attribute" do request.generate(key, :dns_alt_names => 'one, two') extReq = request.content.attributes.find do |attr| attr.oid == 'extReq' end extReq.should be extReq.value.value.all? do |x| x.value.all? do |y| y.value[0].value.should == "subjectAltName" end end end it "should return the subjectAltName values" do request.generate(key, :dns_alt_names => 'one,two') request.subject_alt_names.should =~ ["DNS:myname", "DNS:one", "DNS:two"] end end context "with custom CSR attributes" do it "adds attributes with single values" do csr_attributes = { '1.3.6.1.4.1.34380.1.2.1' => 'CSR specific info', '1.3.6.1.4.1.34380.1.2.2' => 'more CSR specific info', } request.generate(key, :csr_attributes => csr_attributes) attrs = request.custom_attributes attrs.should include({'oid' => '1.3.6.1.4.1.34380.1.2.1', 'value' => 'CSR specific info'}) attrs.should include({'oid' => '1.3.6.1.4.1.34380.1.2.2', 'value' => 'more CSR specific info'}) end ['extReq', '1.2.840.113549.1.9.14'].each do |oid| it "doesn't overwrite standard PKCS#9 CSR attribute '#{oid}'" do expect do request.generate(key, :csr_attributes => {oid => 'data'}) end.to raise_error ArgumentError, /Cannot specify.*#{oid}/ end end ['msExtReq', '1.3.6.1.4.1.311.2.1.14'].each do |oid| it "doesn't overwrite Microsoft extension request OID '#{oid}'" do expect do request.generate(key, :csr_attributes => {oid => 'data'}) end.to raise_error ArgumentError, /Cannot specify.*#{oid}/ end end it "raises an error if an attribute cannot be created" do csr_attributes = { "thats.no.moon" => "death star" } expect do request.generate(key, :csr_attributes => csr_attributes) end.to raise_error Puppet::Error, /Cannot create CSR with attribute thats\.no\.moon: first num too large/ end it "should support old non-DER encoded extensions" do csr = OpenSSL::X509::Request.new(File.read(my_fixture("old-style-cert-request.pem"))) wrapped_csr = Puppet::SSL::CertificateRequest.from_instance csr exts = wrapped_csr.request_extensions() exts.find { |ext| ext['oid'] == 'pp_uuid' }['value'].should == 'I-AM-A-UUID' exts.find { |ext| ext['oid'] == 'pp_instance_id' }['value'].should == 'i_am_an_id' exts.find { |ext| ext['oid'] == 'pp_image_name' }['value'].should == 'i_am_an_image_name' end end context "with extension requests" do let(:extension_data) do { '1.3.6.1.4.1.34380.1.1.31415' => 'pi', '1.3.6.1.4.1.34380.1.1.2718' => 'e', } end it "adds an extreq attribute to the CSR" do request.generate(key, :extension_requests => extension_data) exts = request.content.attributes.select { |attr| attr.oid = 'extReq' } exts.length.should == 1 end it "adds an extension for each entry in the extension request structure" do request.generate(key, :extension_requests => extension_data) exts = request.request_extensions exts.should include('oid' => '1.3.6.1.4.1.34380.1.1.31415', 'value' => 'pi') exts.should include('oid' => '1.3.6.1.4.1.34380.1.1.2718', 'value' => 'e') end it "defines the extensions as non-critical" do request.generate(key, :extension_requests => extension_data) request.request_extensions.each do |ext| - ext['critical'].should be_false + ext['critical'].should be_falsey end end it "rejects the subjectAltNames extension" do san_names = ['subjectAltName', '2.5.29.17'] san_field = 'DNS:first.tld, DNS:second.tld' san_names.each do |name| expect do request.generate(key, :extension_requests => {name => san_field}) end.to raise_error Puppet::Error, /conflicts with internally used extension/ end end it "merges the extReq attribute with the subjectAltNames extension" do request.generate(key, :dns_alt_names => 'first.tld, second.tld', :extension_requests => extension_data) exts = request.request_extensions exts.should include('oid' => '1.3.6.1.4.1.34380.1.1.31415', 'value' => 'pi') exts.should include('oid' => '1.3.6.1.4.1.34380.1.1.2718', 'value' => 'e') exts.should include('oid' => 'subjectAltName', 'value' => 'DNS:first.tld, DNS:myname, DNS:second.tld') request.subject_alt_names.should eq ['DNS:first.tld', 'DNS:myname', 'DNS:second.tld'] end it "raises an error if the OID could not be created" do exts = {"thats.no.moon" => "death star"} expect do request.generate(key, :extension_requests => exts) end.to raise_error Puppet::Error, /Cannot create CSR with extension request thats\.no\.moon: first num too large/ end end it "should sign the csr with the provided key" do request.generate(key) - request.content.verify(key.content.public_key).should be_true + request.content.verify(key.content.public_key).should be_truthy end it "should verify the generated request using the public key" do # Stupid keys don't have a competent == method. OpenSSL::X509::Request.any_instance.expects(:verify).with { |public_key| public_key.to_s == key.content.public_key.to_s }.returns true request.generate(key) end it "should fail if verification fails" do OpenSSL::X509::Request.any_instance.expects(:verify).with { |public_key| public_key.to_s == key.content.public_key.to_s }.returns false expect { request.generate(key) }.to raise_error(Puppet::Error, /CSR sign verification failed/) end it "should log the fingerprint" do Puppet::SSL::Digest.any_instance.stubs(:to_hex).returns("FINGERPRINT") Puppet.stubs(:info) Puppet.expects(:info).with { |s| s =~ /FINGERPRINT/ } request.generate(key) end it "should return the generated request" do generated = request.generate(key) generated.should be_a(OpenSSL::X509::Request) generated.should be(request.content) end it "should use SHA1 to sign the csr when SHA256 isn't available" do csr = OpenSSL::X509::Request.new OpenSSL::Digest.expects(:const_defined?).with("SHA256").returns(false) OpenSSL::Digest.expects(:const_defined?).with("SHA1").returns(true) signer = Puppet::SSL::CertificateSigner.new signer.sign(csr, key.content) - csr.verify(key.content).should be_true + csr.verify(key.content).should be_truthy end it "should raise an error if neither SHA256 nor SHA1 are available" do csr = OpenSSL::X509::Request.new OpenSSL::Digest.expects(:const_defined?).with("SHA256").returns(false) OpenSSL::Digest.expects(:const_defined?).with("SHA1").returns(false) expect { signer = Puppet::SSL::CertificateSigner.new }.to raise_error(Puppet::Error) end end describe "when a CSR is saved" do describe "and a CA is available" do it "should save the CSR and trigger autosigning" do ca = mock 'ca', :autosign Puppet::SSL::CertificateAuthority.expects(:instance).returns ca csr = Puppet::SSL::CertificateRequest.new("me") terminus = mock 'terminus' terminus.stubs(:validate) Puppet::SSL::CertificateRequest.indirection.expects(:prepare).returns(terminus) terminus.expects(:save).with { |request| request.instance == csr && request.key == "me" } Puppet::SSL::CertificateRequest.indirection.save(csr) end end describe "and a CA is not available" do it "should save the CSR" do Puppet::SSL::CertificateAuthority.expects(:instance).returns nil csr = Puppet::SSL::CertificateRequest.new("me") terminus = mock 'terminus' terminus.stubs(:validate) Puppet::SSL::CertificateRequest.indirection.expects(:prepare).returns(terminus) terminus.expects(:save).with { |request| request.instance == csr && request.key == "me" } Puppet::SSL::CertificateRequest.indirection.save(csr) end end end end diff --git a/spec/unit/ssl/certificate_revocation_list_spec.rb b/spec/unit/ssl/certificate_revocation_list_spec.rb index cf25022b0..9b6060828 100755 --- a/spec/unit/ssl/certificate_revocation_list_spec.rb +++ b/spec/unit/ssl/certificate_revocation_list_spec.rb @@ -1,196 +1,196 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/ssl/certificate_revocation_list' describe Puppet::SSL::CertificateRevocationList do before do ca = Puppet::SSL::CertificateAuthority.new ca.generate_ca_certificate @cert = ca.host.certificate.content @key = ca.host.key.content @class = Puppet::SSL::CertificateRevocationList end def expects_time_close_to_now(time) expect(time.to_i).to be_within(5*60).of(Time.now.to_i) end def expects_time_close_to_five_years(time) future = Time.now + Puppet::SSL::CertificateRevocationList::FIVE_YEARS expect(time.to_i).to be_within(5*60).of(future.to_i) end def expects_crlnumber_extension(crl, value) crlNumber = crl.content.extensions.find { |ext| ext.oid == "crlNumber" } expect(crlNumber.value).to eq(value.to_s) expect(crlNumber).to_not be_critical end def expects_authkeyid_extension(crl, cert) subjectKeyId = cert.extensions.find { |ext| ext.oid == 'subjectKeyIdentifier' }.value authKeyId = crl.content.extensions.find { |ext| ext.oid == "authorityKeyIdentifier" } expect(authKeyId.value.chomp).to eq("keyid:#{subjectKeyId}") expect(authKeyId).to_not be_critical end def expects_crlreason_extension(crl, reason) revoke = crl.content.revoked.first crlNumber = crl.content.extensions.find { |ext| ext.oid == "crlNumber" } expect(revoke.serial.to_s).to eq(crlNumber.value) crlReason = revoke.extensions.find { |ext| ext.oid = 'CRLReason' } expect(crlReason.value).to eq(reason) expect(crlReason).to_not be_critical end it "should only support the text format" do @class.supported_formats.should == [:s] end describe "when converting from a string" do it "deserializes a CRL" do crl = @class.new('foo') crl.generate(@cert, @key) new_crl = @class.from_s(crl.to_s) expect(new_crl.content.to_text).to eq(crl.content.to_text) end end describe "when an instance" do before do @crl = @class.new("whatever") end it "should always use 'crl' for its name" do @crl.name.should == "crl" end it "should have a content attribute" do @crl.should respond_to(:content) end end describe "when generating the crl" do before do @crl = @class.new("crl") end it "should set its issuer to the subject of the passed certificate" do @crl.generate(@cert, @key).issuer.to_s.should == @cert.subject.to_s end it "should set its version to 1" do @crl.generate(@cert, @key).version.should == 1 end it "should create an instance of OpenSSL::X509::CRL" do @crl.generate(@cert, @key).should be_an_instance_of(OpenSSL::X509::CRL) end it "should add an extension for the CRL number" do @crl.generate(@cert, @key) expects_crlnumber_extension(@crl, 0) end it "should add an extension for the authority key identifier" do @crl.generate(@cert, @key) expects_authkeyid_extension(@crl, @cert) end it "returns the last update time in UTC" do # http://tools.ietf.org/html/rfc5280#section-5.1.2.4 thisUpdate = @crl.generate(@cert, @key).last_update thisUpdate.should be_utc expects_time_close_to_now(thisUpdate) end it "returns the next update time in UTC 5 years from now" do # http://tools.ietf.org/html/rfc5280#section-5.1.2.5 nextUpdate = @crl.generate(@cert, @key).next_update nextUpdate.should be_utc expects_time_close_to_five_years(nextUpdate) end it "should verify using the CA public_key" do - @crl.generate(@cert, @key).verify(@key.public_key).should be_true + @crl.generate(@cert, @key).verify(@key.public_key).should be_truthy end it "should set the content to the generated crl" do # this test shouldn't be needed since we test the return of generate() which should be the content field @crl.generate(@cert, @key) @crl.content.should be_an_instance_of(OpenSSL::X509::CRL) end end # This test suite isn't exactly complete, because the # SSL stuff is very complicated. It just hits the high points. describe "when revoking a certificate" do before do @crl = @class.new("crl") @crl.generate(@cert, @key) Puppet::SSL::CertificateRevocationList.indirection.stubs :save end it "should require a serial number and the CA's private key" do expect { @crl.revoke }.to raise_error(ArgumentError) end it "should mark the CRL as updated at a time that makes it valid now" do @crl.revoke(1, @key) expects_time_close_to_now(@crl.content.last_update) end it "should mark the CRL valid for five years" do @crl.revoke(1, @key) expects_time_close_to_five_years(@crl.content.next_update) end it "should sign the CRL with the CA's private key and a digest instance" do @crl.content.expects(:sign).with { |key, digest| key == @key and digest.is_a?(OpenSSL::Digest::SHA1) } @crl.revoke(1, @key) end it "should save the CRL" do Puppet::SSL::CertificateRevocationList.indirection.expects(:save).with(@crl, nil) @crl.revoke(1, @key) end it "adds the crlNumber extension containing the serial number" do serial = 1 @crl.revoke(serial, @key) expects_crlnumber_extension(@crl, serial) end it "adds the CA cert's subjectKeyId as the authorityKeyIdentifier to the CRL" do @crl.revoke(1, @key) expects_authkeyid_extension(@crl, @cert) end it "adds a non-critical CRL reason specifying key compromise by default" do # http://tools.ietf.org/html/rfc5280#section-5.3.1 serial = 1 @crl.revoke(serial, @key) expects_crlreason_extension(@crl, 'Key Compromise') end it "allows alternate reasons to be specified" do serial = 1 @crl.revoke(serial, @key, OpenSSL::OCSP::REVOKED_STATUS_CACOMPROMISE) expects_crlreason_extension(@crl, 'CA Compromise') end end end diff --git a/spec/unit/ssl/host_spec.rb b/spec/unit/ssl/host_spec.rb index 280880d1b..fec0d7f87 100755 --- a/spec/unit/ssl/host_spec.rb +++ b/spec/unit/ssl/host_spec.rb @@ -1,940 +1,940 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/ssl/host' require 'matchers/json' def base_pson_comparison(result, pson_hash) result["fingerprint"].should == pson_hash["fingerprint"] result["name"].should == pson_hash["name"] result["state"].should == pson_hash["desired_state"] end describe Puppet::SSL::Host do include JSONMatchers include PuppetSpec::Files before do Puppet::SSL::Host.indirection.terminus_class = :file # Get a safe temporary file dir = tmpdir("ssl_host_testing") Puppet.settings[:confdir] = dir Puppet.settings[:vardir] = dir Puppet.settings.use :main, :ssl @host = Puppet::SSL::Host.new("myname") end after do # Cleaned out any cached localhost instance. Puppet::SSL::Host.reset Puppet::SSL::Host.ca_location = :none end it "should use any provided name as its name" do @host.name.should == "myname" end it "should retrieve its public key from its private key" do realkey = mock 'realkey' key = stub 'key', :content => realkey Puppet::SSL::Key.indirection.stubs(:find).returns(key) pubkey = mock 'public_key' realkey.expects(:public_key).returns pubkey @host.public_key.should equal(pubkey) end it "should default to being a non-ca host" do - @host.ca?.should be_false + @host.ca?.should be_falsey end it "should be a ca host if its name matches the CA_NAME" do Puppet::SSL::Host.stubs(:ca_name).returns "yayca" Puppet::SSL::Host.new("yayca").should be_ca end it "should have a method for determining the CA location" do Puppet::SSL::Host.should respond_to(:ca_location) end it "should have a method for specifying the CA location" do Puppet::SSL::Host.should respond_to(:ca_location=) end it "should have a method for retrieving the default ssl host" do Puppet::SSL::Host.should respond_to(:ca_location=) end it "should have a method for producing an instance to manage the local host's keys" do Puppet::SSL::Host.should respond_to(:localhost) end it "should allow to reset localhost" do previous_host = Puppet::SSL::Host.localhost Puppet::SSL::Host.reset Puppet::SSL::Host.localhost.should_not == previous_host end it "should generate the certificate for the localhost instance if no certificate is available" do host = stub 'host', :key => nil Puppet::SSL::Host.expects(:new).returns host host.expects(:certificate).returns nil host.expects(:generate) Puppet::SSL::Host.localhost.should equal(host) end it "should create a localhost cert if no cert is available and it is a CA with autosign and it is using DNS alt names", :unless => Puppet.features.microsoft_windows? do Puppet[:autosign] = true Puppet[:confdir] = tmpdir('conf') Puppet[:dns_alt_names] = "foo,bar,baz" ca = Puppet::SSL::CertificateAuthority.new Puppet::SSL::CertificateAuthority.stubs(:instance).returns ca localhost = Puppet::SSL::Host.localhost cert = localhost.certificate cert.should be_a(Puppet::SSL::Certificate) cert.subject_alt_names.should =~ %W[DNS:#{Puppet[:certname]} DNS:foo DNS:bar DNS:baz] end context "with dns_alt_names" do before :each do @key = stub('key content') key = stub('key', :generate => true, :content => @key) Puppet::SSL::Key.stubs(:new).returns key Puppet::SSL::Key.indirection.stubs(:save).with(key) @cr = stub('certificate request') Puppet::SSL::CertificateRequest.stubs(:new).returns @cr Puppet::SSL::CertificateRequest.indirection.stubs(:save).with(@cr) end describe "explicitly specified" do before :each do Puppet[:dns_alt_names] = 'one, two' end it "should not include subjectAltName if not the local node" do @cr.expects(:generate).with(@key, {}) Puppet::SSL::Host.new('not-the-' + Puppet[:certname]).generate end it "should include subjectAltName if I am a CA" do @cr.expects(:generate). with(@key, { :dns_alt_names => Puppet[:dns_alt_names] }) Puppet::SSL::Host.localhost end end describe "implicitly defaulted" do let(:ca) { stub('ca', :sign => nil) } before :each do Puppet[:dns_alt_names] = '' Puppet::SSL::CertificateAuthority.stubs(:instance).returns ca end it "should not include defaults if we're not the CA" do Puppet::SSL::CertificateAuthority.stubs(:ca?).returns false @cr.expects(:generate).with(@key, {}) Puppet::SSL::Host.localhost end it "should not include defaults if not the local node" do Puppet::SSL::CertificateAuthority.stubs(:ca?).returns true @cr.expects(:generate).with(@key, {}) Puppet::SSL::Host.new('not-the-' + Puppet[:certname]).generate end it "should not include defaults if we can't resolve our fqdn" do Puppet::SSL::CertificateAuthority.stubs(:ca?).returns true Facter.stubs(:value).with(:fqdn).returns nil @cr.expects(:generate).with(@key, {}) Puppet::SSL::Host.localhost end it "should provide defaults if we're bootstrapping the local master" do Puppet::SSL::CertificateAuthority.stubs(:ca?).returns true Facter.stubs(:value).with(:fqdn).returns 'web.foo.com' Facter.stubs(:value).with(:domain).returns 'foo.com' @cr.expects(:generate).with(@key, {:dns_alt_names => "puppet, web.foo.com, puppet.foo.com"}) Puppet::SSL::Host.localhost end end end it "should always read the key for the localhost instance in from disk" do host = stub 'host', :certificate => "eh" Puppet::SSL::Host.expects(:new).returns host host.expects(:key) Puppet::SSL::Host.localhost end it "should cache the localhost instance" do host = stub 'host', :certificate => "eh", :key => 'foo' Puppet::SSL::Host.expects(:new).once.returns host Puppet::SSL::Host.localhost.should == Puppet::SSL::Host.localhost end it "should be able to verify its certificate matches its key" do Puppet::SSL::Host.new("foo").should respond_to(:validate_certificate_with_key) end it "should consider the certificate invalid if it cannot find a key" do host = Puppet::SSL::Host.new("foo") certificate = mock('cert', :fingerprint => 'DEADBEEF') host.expects(:certificate).twice.returns certificate host.expects(:key).returns nil lambda { host.validate_certificate_with_key }.should raise_error(Puppet::Error, "No private key with which to validate certificate with fingerprint: DEADBEEF") end it "should consider the certificate invalid if it cannot find a certificate" do host = Puppet::SSL::Host.new("foo") host.expects(:key).never host.expects(:certificate).returns nil lambda { host.validate_certificate_with_key }.should raise_error(Puppet::Error, "No certificate to validate.") end it "should consider the certificate invalid if the SSL certificate's key verification fails" do host = Puppet::SSL::Host.new("foo") key = mock 'key', :content => "private_key" sslcert = mock 'sslcert' certificate = mock 'cert', {:content => sslcert, :fingerprint => 'DEADBEEF'} host.stubs(:key).returns key host.stubs(:certificate).returns certificate sslcert.expects(:check_private_key).with("private_key").returns false lambda { host.validate_certificate_with_key }.should raise_error(Puppet::Error, /DEADBEEF/) end it "should consider the certificate valid if the SSL certificate's key verification succeeds" do host = Puppet::SSL::Host.new("foo") key = mock 'key', :content => "private_key" sslcert = mock 'sslcert' certificate = mock 'cert', :content => sslcert host.stubs(:key).returns key host.stubs(:certificate).returns certificate sslcert.expects(:check_private_key).with("private_key").returns true lambda{ host.validate_certificate_with_key }.should_not raise_error end describe "when specifying the CA location" do it "should support the location ':local'" do lambda { Puppet::SSL::Host.ca_location = :local }.should_not raise_error end it "should support the location ':remote'" do lambda { Puppet::SSL::Host.ca_location = :remote }.should_not raise_error end it "should support the location ':none'" do lambda { Puppet::SSL::Host.ca_location = :none }.should_not raise_error end it "should support the location ':only'" do lambda { Puppet::SSL::Host.ca_location = :only }.should_not raise_error end it "should not support other modes" do lambda { Puppet::SSL::Host.ca_location = :whatever }.should raise_error(ArgumentError) end describe "as 'local'" do before do Puppet::SSL::Host.ca_location = :local end it "should set the cache class for Certificate, CertificateRevocationList, and CertificateRequest as :file" do Puppet::SSL::Certificate.indirection.cache_class.should == :file Puppet::SSL::CertificateRequest.indirection.cache_class.should == :file Puppet::SSL::CertificateRevocationList.indirection.cache_class.should == :file end it "should set the terminus class for Key and Host as :file" do Puppet::SSL::Key.indirection.terminus_class.should == :file Puppet::SSL::Host.indirection.terminus_class.should == :file end it "should set the terminus class for Certificate, CertificateRevocationList, and CertificateRequest as :ca" do Puppet::SSL::Certificate.indirection.terminus_class.should == :ca Puppet::SSL::CertificateRequest.indirection.terminus_class.should == :ca Puppet::SSL::CertificateRevocationList.indirection.terminus_class.should == :ca end end describe "as 'remote'" do before do Puppet::SSL::Host.ca_location = :remote end it "should set the cache class for Certificate, CertificateRevocationList, and CertificateRequest as :file" do Puppet::SSL::Certificate.indirection.cache_class.should == :file Puppet::SSL::CertificateRequest.indirection.cache_class.should == :file Puppet::SSL::CertificateRevocationList.indirection.cache_class.should == :file end it "should set the terminus class for Key as :file" do Puppet::SSL::Key.indirection.terminus_class.should == :file end it "should set the terminus class for Host, Certificate, CertificateRevocationList, and CertificateRequest as :rest" do Puppet::SSL::Host.indirection.terminus_class.should == :rest Puppet::SSL::Certificate.indirection.terminus_class.should == :rest Puppet::SSL::CertificateRequest.indirection.terminus_class.should == :rest Puppet::SSL::CertificateRevocationList.indirection.terminus_class.should == :rest end end describe "as 'only'" do before do Puppet::SSL::Host.ca_location = :only end it "should set the terminus class for Key, Certificate, CertificateRevocationList, and CertificateRequest as :ca" do Puppet::SSL::Key.indirection.terminus_class.should == :ca Puppet::SSL::Certificate.indirection.terminus_class.should == :ca Puppet::SSL::CertificateRequest.indirection.terminus_class.should == :ca Puppet::SSL::CertificateRevocationList.indirection.terminus_class.should == :ca end it "should set the cache class for Certificate, CertificateRevocationList, and CertificateRequest to nil" do Puppet::SSL::Certificate.indirection.cache_class.should be_nil Puppet::SSL::CertificateRequest.indirection.cache_class.should be_nil Puppet::SSL::CertificateRevocationList.indirection.cache_class.should be_nil end it "should set the terminus class for Host to :file" do Puppet::SSL::Host.indirection.terminus_class.should == :file end end describe "as 'none'" do before do Puppet::SSL::Host.ca_location = :none end it "should set the terminus class for Key, Certificate, CertificateRevocationList, and CertificateRequest as :file" do Puppet::SSL::Key.indirection.terminus_class.should == :disabled_ca Puppet::SSL::Certificate.indirection.terminus_class.should == :disabled_ca Puppet::SSL::CertificateRequest.indirection.terminus_class.should == :disabled_ca Puppet::SSL::CertificateRevocationList.indirection.terminus_class.should == :disabled_ca end it "should set the terminus class for Host to 'none'" do lambda { Puppet::SSL::Host.indirection.terminus_class }.should raise_error(Puppet::DevError) end end end it "should have a class method for destroying all files related to a given host" do Puppet::SSL::Host.should respond_to(:destroy) end describe "when destroying a host's SSL files" do before do Puppet::SSL::Key.indirection.stubs(:destroy).returns false Puppet::SSL::Certificate.indirection.stubs(:destroy).returns false Puppet::SSL::CertificateRequest.indirection.stubs(:destroy).returns false end it "should destroy its certificate, certificate request, and key" do Puppet::SSL::Key.indirection.expects(:destroy).with("myhost") Puppet::SSL::Certificate.indirection.expects(:destroy).with("myhost") Puppet::SSL::CertificateRequest.indirection.expects(:destroy).with("myhost") Puppet::SSL::Host.destroy("myhost") end it "should return true if any of the classes returned true" do Puppet::SSL::Certificate.indirection.expects(:destroy).with("myhost").returns true - Puppet::SSL::Host.destroy("myhost").should be_true + Puppet::SSL::Host.destroy("myhost").should be_truthy end it "should report that nothing was deleted if none of the classes returned true" do Puppet::SSL::Host.destroy("myhost").should == "Nothing was deleted" end end describe "when initializing" do it "should default its name to the :certname setting" do Puppet[:certname] = "myname" Puppet::SSL::Host.new.name.should == "myname" end it "should downcase a passed in name" do Puppet::SSL::Host.new("Host.Domain.Com").name.should == "host.domain.com" end it "should indicate that it is a CA host if its name matches the ca_name constant" do Puppet::SSL::Host.stubs(:ca_name).returns "myca" Puppet::SSL::Host.new("myca").should be_ca end end describe "when managing its private key" do before do @realkey = "mykey" @key = Puppet::SSL::Key.new("mykey") @key.content = @realkey end it "should return nil if the key is not set and cannot be found" do Puppet::SSL::Key.indirection.expects(:find).with("myname").returns(nil) @host.key.should be_nil end it "should find the key in the Key class and return the Puppet instance" do Puppet::SSL::Key.indirection.expects(:find).with("myname").returns(@key) @host.key.should equal(@key) end it "should be able to generate and save a new key" do Puppet::SSL::Key.expects(:new).with("myname").returns(@key) @key.expects(:generate) Puppet::SSL::Key.indirection.expects(:save) - @host.generate_key.should be_true + @host.generate_key.should be_truthy @host.key.should equal(@key) end it "should not retain keys that could not be saved" do Puppet::SSL::Key.expects(:new).with("myname").returns(@key) @key.stubs(:generate) Puppet::SSL::Key.indirection.expects(:save).raises "eh" lambda { @host.generate_key }.should raise_error @host.key.should be_nil end it "should return any previously found key without requerying" do Puppet::SSL::Key.indirection.expects(:find).with("myname").returns(@key).once @host.key.should equal(@key) @host.key.should equal(@key) end end describe "when managing its certificate request" do before do @realrequest = "real request" @request = Puppet::SSL::CertificateRequest.new("myname") @request.content = @realrequest end it "should return nil if the key is not set and cannot be found" do Puppet::SSL::CertificateRequest.indirection.expects(:find).with("myname").returns(nil) @host.certificate_request.should be_nil end it "should find the request in the Key class and return it and return the Puppet SSL request" do Puppet::SSL::CertificateRequest.indirection.expects(:find).with("myname").returns @request @host.certificate_request.should equal(@request) end it "should generate a new key when generating the cert request if no key exists" do Puppet::SSL::CertificateRequest.expects(:new).with("myname").returns @request key = stub 'key', :public_key => mock("public_key"), :content => "mycontent" @host.expects(:key).times(2).returns(nil).then.returns(key) @host.expects(:generate_key).returns(key) @request.stubs(:generate) Puppet::SSL::CertificateRequest.indirection.stubs(:save) @host.generate_certificate_request end it "should be able to generate and save a new request using the private key" do Puppet::SSL::CertificateRequest.expects(:new).with("myname").returns @request key = stub 'key', :public_key => mock("public_key"), :content => "mycontent" @host.stubs(:key).returns(key) @request.expects(:generate).with("mycontent", {}) Puppet::SSL::CertificateRequest.indirection.expects(:save).with(@request) - @host.generate_certificate_request.should be_true + @host.generate_certificate_request.should be_truthy @host.certificate_request.should equal(@request) end it "should return any previously found request without requerying" do Puppet::SSL::CertificateRequest.indirection.expects(:find).with("myname").returns(@request).once @host.certificate_request.should equal(@request) @host.certificate_request.should equal(@request) end it "should not keep its certificate request in memory if the request cannot be saved" do Puppet::SSL::CertificateRequest.expects(:new).with("myname").returns @request key = stub 'key', :public_key => mock("public_key"), :content => "mycontent" @host.stubs(:key).returns(key) @request.stubs(:generate) @request.stubs(:name).returns("myname") terminus = stub 'terminus' terminus.stubs(:validate) Puppet::SSL::CertificateRequest.indirection.expects(:prepare).returns(terminus) terminus.expects(:save).with { |req| req.instance == @request && req.key == "myname" }.raises "eh" lambda { @host.generate_certificate_request }.should raise_error @host.instance_eval { @certificate_request }.should be_nil end end describe "when managing its certificate" do before do @realcert = mock 'certificate' @cert = stub 'cert', :content => @realcert @host.stubs(:key).returns mock("key") @host.stubs(:validate_certificate_with_key) end it "should find the CA certificate if it does not have a certificate" do Puppet::SSL::Certificate.indirection.expects(:find).with(Puppet::SSL::CA_NAME, :fail_on_404 => true).returns mock("cacert") Puppet::SSL::Certificate.indirection.stubs(:find).with("myname").returns @cert @host.certificate end it "should not find the CA certificate if it is the CA host" do @host.expects(:ca?).returns true Puppet::SSL::Certificate.indirection.stubs(:find) Puppet::SSL::Certificate.indirection.expects(:find).with(Puppet::SSL::CA_NAME, :fail_on_404 => true).never @host.certificate end it "should return nil if it cannot find a CA certificate" do Puppet::SSL::Certificate.indirection.expects(:find).with(Puppet::SSL::CA_NAME, :fail_on_404 => true).returns nil Puppet::SSL::Certificate.indirection.expects(:find).with("myname").never @host.certificate.should be_nil end it "should find the key if it does not have one" do Puppet::SSL::Certificate.indirection.stubs(:find) @host.expects(:key).returns mock("key") @host.certificate end it "should generate the key if one cannot be found" do Puppet::SSL::Certificate.indirection.stubs(:find) @host.expects(:key).returns nil @host.expects(:generate_key) @host.certificate end it "should find the certificate in the Certificate class and return the Puppet certificate instance" do Puppet::SSL::Certificate.indirection.expects(:find).with(Puppet::SSL::CA_NAME, :fail_on_404 => true).returns mock("cacert") Puppet::SSL::Certificate.indirection.expects(:find).with("myname").returns @cert @host.certificate.should equal(@cert) end it "should return any previously found certificate" do Puppet::SSL::Certificate.indirection.expects(:find).with(Puppet::SSL::CA_NAME, :fail_on_404 => true).returns mock("cacert") Puppet::SSL::Certificate.indirection.expects(:find).with("myname").returns(@cert).once @host.certificate.should equal(@cert) @host.certificate.should equal(@cert) end end it "should have a method for listing certificate hosts" do Puppet::SSL::Host.should respond_to(:search) end describe "when listing certificate hosts" do it "should default to listing all clients with any file types" do Puppet::SSL::Key.indirection.expects(:search).returns [] Puppet::SSL::Certificate.indirection.expects(:search).returns [] Puppet::SSL::CertificateRequest.indirection.expects(:search).returns [] Puppet::SSL::Host.search end it "should be able to list only clients with a key" do Puppet::SSL::Key.indirection.expects(:search).returns [] Puppet::SSL::Certificate.indirection.expects(:search).never Puppet::SSL::CertificateRequest.indirection.expects(:search).never Puppet::SSL::Host.search :for => Puppet::SSL::Key end it "should be able to list only clients with a certificate" do Puppet::SSL::Key.indirection.expects(:search).never Puppet::SSL::Certificate.indirection.expects(:search).returns [] Puppet::SSL::CertificateRequest.indirection.expects(:search).never Puppet::SSL::Host.search :for => Puppet::SSL::Certificate end it "should be able to list only clients with a certificate request" do Puppet::SSL::Key.indirection.expects(:search).never Puppet::SSL::Certificate.indirection.expects(:search).never Puppet::SSL::CertificateRequest.indirection.expects(:search).returns [] Puppet::SSL::Host.search :for => Puppet::SSL::CertificateRequest end it "should return a Host instance created with the name of each found instance" do key = stub 'key', :name => "key", :to_ary => nil cert = stub 'cert', :name => "cert", :to_ary => nil csr = stub 'csr', :name => "csr", :to_ary => nil Puppet::SSL::Key.indirection.expects(:search).returns [key] Puppet::SSL::Certificate.indirection.expects(:search).returns [cert] Puppet::SSL::CertificateRequest.indirection.expects(:search).returns [csr] returned = [] %w{key cert csr}.each do |name| result = mock(name) returned << result Puppet::SSL::Host.expects(:new).with(name).returns result end result = Puppet::SSL::Host.search returned.each do |r| result.should be_include(r) end end end it "should have a method for generating all necessary files" do Puppet::SSL::Host.new("me").should respond_to(:generate) end describe "when generating files" do before do @host = Puppet::SSL::Host.new("me") @host.stubs(:generate_key) @host.stubs(:generate_certificate_request) end it "should generate a key if one is not present" do @host.stubs(:key).returns nil @host.expects(:generate_key) @host.generate end it "should generate a certificate request if one is not present" do @host.expects(:certificate_request).returns nil @host.expects(:generate_certificate_request) @host.generate end describe "and it can create a certificate authority" do before do @ca = mock 'ca' Puppet::SSL::CertificateAuthority.stubs(:instance).returns @ca end it "should use the CA to sign its certificate request if it does not have a certificate" do @host.expects(:certificate).returns nil @ca.expects(:sign).with(@host.name, true) @host.generate end end describe "and it cannot create a certificate authority" do before do Puppet::SSL::CertificateAuthority.stubs(:instance).returns nil end it "should seek its certificate" do @host.expects(:certificate) @host.generate end end end it "should have a method for creating an SSL store" do Puppet::SSL::Host.new("me").should respond_to(:ssl_store) end it "should always return the same store" do host = Puppet::SSL::Host.new("foo") store = mock 'store' store.stub_everything OpenSSL::X509::Store.expects(:new).returns store host.ssl_store.should equal(host.ssl_store) end describe "when creating an SSL store" do before do @host = Puppet::SSL::Host.new("me") @store = mock 'store' @store.stub_everything OpenSSL::X509::Store.stubs(:new).returns @store Puppet[:localcacert] = "ssl_host_testing" Puppet::SSL::CertificateRevocationList.indirection.stubs(:find).returns(nil) end it "should accept a purpose" do @store.expects(:purpose=).with "my special purpose" @host.ssl_store("my special purpose") end it "should default to OpenSSL::X509::PURPOSE_ANY as the purpose" do @store.expects(:purpose=).with OpenSSL::X509::PURPOSE_ANY @host.ssl_store end it "should add the local CA cert file" do Puppet[:localcacert] = "/ca/cert/file" @store.expects(:add_file).with Puppet[:localcacert] @host.ssl_store end describe "and a CRL is available" do before do @crl = stub 'crl', :content => "real_crl" Puppet::SSL::CertificateRevocationList.indirection.stubs(:find).returns @crl end describe "and 'certificate_revocation' is true" do before do Puppet[:certificate_revocation] = true end it "should add the CRL" do @store.expects(:add_crl).with "real_crl" @host.ssl_store end it "should set the flags to OpenSSL::X509::V_FLAG_CRL_CHECK_ALL|OpenSSL::X509::V_FLAG_CRL_CHECK" do @store.expects(:flags=).with OpenSSL::X509::V_FLAG_CRL_CHECK_ALL|OpenSSL::X509::V_FLAG_CRL_CHECK @host.ssl_store end end describe "and 'certificate_revocation' is false" do before do Puppet[:certificate_revocation] = false end it "should not add the CRL" do @store.expects(:add_crl).never @host.ssl_store end it "should not set the flags" do @store.expects(:flags=).never @host.ssl_store end end end end describe "when waiting for a cert" do before do @host = Puppet::SSL::Host.new("me") end it "should generate its certificate request and attempt to read the certificate again if no certificate is found" do @host.expects(:certificate).times(2).returns(nil).then.returns "foo" @host.expects(:generate) @host.wait_for_cert(1) end it "should catch and log errors during CSR saving" do @host.expects(:certificate).times(2).returns(nil).then.returns "foo" @host.expects(:generate).raises(RuntimeError).then.returns nil @host.stubs(:sleep) @host.wait_for_cert(1) end it "should sleep and retry after failures saving the CSR if waitforcert is enabled" do @host.expects(:certificate).times(2).returns(nil).then.returns "foo" @host.expects(:generate).raises(RuntimeError).then.returns nil @host.expects(:sleep).with(1) @host.wait_for_cert(1) end it "should exit after failures saving the CSR of waitforcert is disabled" do @host.expects(:certificate).returns(nil) @host.expects(:generate).raises(RuntimeError) @host.expects(:puts) expect { @host.wait_for_cert(0) }.to exit_with 1 end it "should exit if the wait time is 0 and it can neither find nor retrieve a certificate" do @host.stubs(:certificate).returns nil @host.expects(:generate) @host.expects(:puts) expect { @host.wait_for_cert(0) }.to exit_with 1 end it "should sleep for the specified amount of time if no certificate is found after generating its certificate request" do @host.expects(:certificate).times(3).returns(nil).then.returns(nil).then.returns "foo" @host.expects(:generate) @host.expects(:sleep).with(1) @host.wait_for_cert(1) end it "should catch and log exceptions during certificate retrieval" do @host.expects(:certificate).times(3).returns(nil).then.raises(RuntimeError).then.returns("foo") @host.stubs(:generate) @host.stubs(:sleep) Puppet.expects(:err) @host.wait_for_cert(1) end end describe "when handling PSON", :unless => Puppet.features.microsoft_windows? do include PuppetSpec::Files before do Puppet[:vardir] = tmpdir("ssl_test_vardir") Puppet[:ssldir] = tmpdir("ssl_test_ssldir") # localcacert is where each client stores the CA certificate # cacert is where the master stores the CA certificate # Since we need to play the role of both for testing we need them to be the same and exist Puppet[:cacert] = Puppet[:localcacert] @ca=Puppet::SSL::CertificateAuthority.new end describe "when converting to PSON" do let(:host) do Puppet::SSL::Host.new("bazinga") end let(:pson_hash) do { "fingerprint" => host.certificate_request.fingerprint, "desired_state" => 'requested', "name" => host.name } end it "should be able to identify a host with an unsigned certificate request" do host.generate_certificate_request result = PSON.parse(Puppet::SSL::Host.new(host.name).to_pson) base_pson_comparison result, pson_hash end it "should validate against the schema" do host.generate_certificate_request expect(host.to_pson).to validate_against('api/schemas/host.json') end describe "explicit fingerprints" do [:SHA1, :SHA256, :SHA512].each do |md| it "should include #{md}" do mds = md.to_s host.generate_certificate_request pson_hash["fingerprints"] = {} pson_hash["fingerprints"][mds] = host.certificate_request.fingerprint(md) result = PSON.parse(Puppet::SSL::Host.new(host.name).to_pson) base_pson_comparison result, pson_hash result["fingerprints"][mds].should == pson_hash["fingerprints"][mds] end end end describe "dns_alt_names" do describe "when not specified" do it "should include the dns_alt_names associated with the certificate" do host.generate_certificate_request pson_hash["desired_alt_names"] = host.certificate_request.subject_alt_names result = PSON.parse(Puppet::SSL::Host.new(host.name).to_pson) base_pson_comparison result, pson_hash result["dns_alt_names"].should == pson_hash["desired_alt_names"] end end [ "", "test, alt, names" ].each do |alt_names| describe "when #{alt_names}" do before(:each) do host.generate_certificate_request :dns_alt_names => alt_names end it "should include the dns_alt_names associated with the certificate" do pson_hash["desired_alt_names"] = host.certificate_request.subject_alt_names result = PSON.parse(Puppet::SSL::Host.new(host.name).to_pson) base_pson_comparison result, pson_hash result["dns_alt_names"].should == pson_hash["desired_alt_names"] end it "should validate against the schema" do expect(host.to_pson).to validate_against('api/schemas/host.json') end end end end it "should be able to identify a host with a signed certificate" do host.generate_certificate_request @ca.sign(host.name) pson_hash = { "fingerprint" => Puppet::SSL::Certificate.indirection.find(host.name).fingerprint, "desired_state" => 'signed', "name" => host.name, } result = PSON.parse(Puppet::SSL::Host.new(host.name).to_pson) base_pson_comparison result, pson_hash end it "should be able to identify a host with a revoked certificate" do host.generate_certificate_request @ca.sign(host.name) @ca.revoke(host.name) pson_hash["fingerprint"] = Puppet::SSL::Certificate.indirection.find(host.name).fingerprint pson_hash["desired_state"] = 'revoked' result = PSON.parse(Puppet::SSL::Host.new(host.name).to_pson) base_pson_comparison result, pson_hash end end describe "when converting from PSON" do it "should return a Puppet::SSL::Host object with the specified desired state" do host = Puppet::SSL::Host.new("bazinga") host.desired_state="signed" pson_hash = { "name" => host.name, "desired_state" => host.desired_state, } generated_host = Puppet::SSL::Host.from_data_hash(pson_hash) generated_host.desired_state.should == host.desired_state generated_host.name.should == host.name end end end end diff --git a/spec/unit/ssl/oids_spec.rb b/spec/unit/ssl/oids_spec.rb index ee95bb942..a3fffe0bc 100644 --- a/spec/unit/ssl/oids_spec.rb +++ b/spec/unit/ssl/oids_spec.rb @@ -1,48 +1,48 @@ require 'spec_helper' require 'puppet/ssl/oids' describe Puppet::SSL::Oids do describe "defining application OIDs" do { 'puppetlabs' => '1.3.6.1.4.1.34380', 'ppCertExt' => '1.3.6.1.4.1.34380.1', 'ppRegCertExt' => '1.3.6.1.4.1.34380.1.1', 'pp_uuid' => '1.3.6.1.4.1.34380.1.1.1', 'pp_instance_id' => '1.3.6.1.4.1.34380.1.1.2', 'pp_image_name' => '1.3.6.1.4.1.34380.1.1.3', 'pp_preshared_key' => '1.3.6.1.4.1.34380.1.1.4', 'ppPrivCertExt' => '1.3.6.1.4.1.34380.1.2', }.each_pair do |sn, oid| it "defines #{sn} as #{oid}" do object_id = OpenSSL::ASN1::ObjectId.new(sn) expect(object_id.oid).to eq oid end end end describe "checking if an OID is a subtree of another OID" do it "can determine if an OID is contained in another OID" do - described_class.subtree_of?('1.3.6.1', '1.3.6.1.4.1').should be_true - described_class.subtree_of?('1.3.6.1.4.1', '1.3.6.1').should be_false + described_class.subtree_of?('1.3.6.1', '1.3.6.1.4.1').should be_truthy + described_class.subtree_of?('1.3.6.1.4.1', '1.3.6.1').should be_falsey end it "returns true if an OID is compared against itself and exclusive is false" do - described_class.subtree_of?('1.3.6.1', '1.3.6.1', false).should be_true + described_class.subtree_of?('1.3.6.1', '1.3.6.1', false).should be_truthy end it "returns false if an OID is compared against itself and exclusive is true" do - described_class.subtree_of?('1.3.6.1', '1.3.6.1', true).should be_false + described_class.subtree_of?('1.3.6.1', '1.3.6.1', true).should be_falsey end it "can compare OIDs defined as short names" do - described_class.subtree_of?('IANA', '1.3.6.1.4.1').should be_true - described_class.subtree_of?('1.3.6.1', 'enterprises').should be_true + described_class.subtree_of?('IANA', '1.3.6.1.4.1').should be_truthy + described_class.subtree_of?('1.3.6.1', 'enterprises').should be_truthy end it "returns false when an invalid OID shortname is passed" do - described_class.subtree_of?('IANA', 'bananas').should be_false + described_class.subtree_of?('IANA', 'bananas').should be_falsey end end end diff --git a/spec/unit/ssl/validator_spec.rb b/spec/unit/ssl/validator_spec.rb index b09242abc..80d7c75d4 100644 --- a/spec/unit/ssl/validator_spec.rb +++ b/spec/unit/ssl/validator_spec.rb @@ -1,418 +1,418 @@ require 'spec_helper' require 'puppet/ssl' describe Puppet::SSL::Validator::DefaultValidator do let(:ssl_context) do mock('OpenSSL::X509::StoreContext') end let(:ssl_configuration) do Puppet::SSL::Configuration.new( Puppet[:localcacert], :ca_auth_file => Puppet[:ssl_client_ca_auth]) end let(:ssl_host) do stub('ssl_host', :ssl_store => nil, :certificate => stub('cert', :content => nil), :key => stub('key', :content => nil)) end subject do described_class.new(ssl_configuration, ssl_host) end before :each do ssl_configuration.stubs(:read_file). with(Puppet[:localcacert]). returns(root_ca) end describe '#call' do before :each do ssl_context.stubs(:current_cert).returns(*cert_chain_in_callback_order) ssl_context.stubs(:chain).returns(cert_chain) end context 'When pre-verification is not OK' do context 'and the ssl_context is in an error state' do let(:root_subject) { OpenSSL::X509::Certificate.new(root_ca).subject.to_s } let(:code) { OpenSSL::X509::V_ERR_INVALID_CA } it 'rejects the connection' do ssl_context.stubs(:error_string).returns("Something went wrong") ssl_context.stubs(:error).returns(code) expect(subject.call(false, ssl_context)).to eq(false) end it 'makes the error available via #verify_errors' do ssl_context.stubs(:error_string).returns("Something went wrong") ssl_context.stubs(:error).returns(code) subject.call(false, ssl_context) expect(subject.verify_errors).to eq(["Something went wrong for #{root_subject}"]) end it 'uses a generic message if error_string is nil' do ssl_context.stubs(:error_string).returns(nil) ssl_context.stubs(:error).returns(code) subject.call(false, ssl_context) expect(subject.verify_errors).to eq(["OpenSSL error #{code} for #{root_subject}"]) end it 'uses 0 for nil error codes' do ssl_context.stubs(:error_string).returns("Something went wrong") ssl_context.stubs(:error).returns(nil) subject.call(false, ssl_context) expect(subject.verify_errors).to eq(["Something went wrong for #{root_subject}"]) end context "when CRL is not yet valid" do before :each do ssl_context.stubs(:error_string).returns("CRL is not yet valid") ssl_context.stubs(:error).returns(OpenSSL::X509::V_ERR_CRL_NOT_YET_VALID) end it 'rejects nil CRL' do ssl_context.stubs(:current_crl).returns(nil) expect(subject.call(false, ssl_context)).to eq(false) expect(subject.verify_errors).to eq(["CRL is not yet valid"]) end it 'includes the CRL issuer in the verify error message' do crl = OpenSSL::X509::CRL.new crl.issuer = OpenSSL::X509::Name.new([['CN','Puppet CA: puppetmaster.example.com']]) crl.last_update = Time.now + 24 * 60 * 60 ssl_context.stubs(:current_crl).returns(crl) subject.call(false, ssl_context) expect(subject.verify_errors).to eq(["CRL is not yet valid for /CN=Puppet CA: puppetmaster.example.com"]) end it 'rejects CRLs whose last_update time is more than 5 minutes in the future' do crl = OpenSSL::X509::CRL.new crl.issuer = OpenSSL::X509::Name.new([['CN','Puppet CA: puppetmaster.example.com']]) crl.last_update = Time.now + 24 * 60 * 60 ssl_context.stubs(:current_crl).returns(crl) expect(subject.call(false, ssl_context)).to eq(false) end it 'accepts CRLs whose last_update time is 10 seconds in the future' do crl = OpenSSL::X509::CRL.new crl.issuer = OpenSSL::X509::Name.new([['CN','Puppet CA: puppetmaster.example.com']]) crl.last_update = Time.now + 10 ssl_context.stubs(:current_crl).returns(crl) expect(subject.call(false, ssl_context)).to eq(true) end end end end context 'When pre-verification is OK' do context 'and the ssl_context is in an error state' do before :each do ssl_context.stubs(:error_string).returns("Something went wrong") end it 'does not make the error available via #verify_errors' do subject.call(true, ssl_context) subject.verify_errors.should == [] end end context 'and the chain is valid' do it 'is true for each CA certificate in the chain' do (cert_chain.length - 1).times do - subject.call(true, ssl_context).should be_true + subject.call(true, ssl_context).should be_truthy end end it 'is true for the SSL certificate ending the chain' do (cert_chain.length - 1).times do subject.call(true, ssl_context) end - subject.call(true, ssl_context).should be_true + subject.call(true, ssl_context).should be_truthy end end context 'and the chain is invalid' do before :each do ssl_configuration.stubs(:read_file). with(Puppet[:localcacert]). returns(agent_ca) end it 'is true for each CA certificate in the chain' do (cert_chain.length - 1).times do - subject.call(true, ssl_context).should be_true + subject.call(true, ssl_context).should be_truthy end end it 'is false for the SSL certificate ending the chain' do (cert_chain.length - 1).times do subject.call(true, ssl_context) end - subject.call(true, ssl_context).should be_false + subject.call(true, ssl_context).should be_falsey end end context 'an error is raised inside of #call' do before :each do ssl_context.expects(:current_cert).raises(StandardError, "BOOM!") end it 'is false' do - subject.call(true, ssl_context).should be_false + subject.call(true, ssl_context).should be_falsey end it 'makes the error available through #verify_errors' do subject.call(true, ssl_context) subject.verify_errors.should == ["BOOM!"] end end end end describe '#setup_connection' do it 'updates the connection for verification' do subject.stubs(:ssl_certificates_are_present?).returns(true) connection = mock('Net::HTTP') connection.expects(:cert_store=).with(ssl_host.ssl_store) connection.expects(:ca_file=).with(ssl_configuration.ca_auth_file) connection.expects(:cert=).with(ssl_host.certificate.content) connection.expects(:key=).with(ssl_host.key.content) connection.expects(:verify_callback=).with(subject) connection.expects(:verify_mode=).with(OpenSSL::SSL::VERIFY_PEER) subject.setup_connection(connection) end it 'does not perform verification if certificate files are missing' do subject.stubs(:ssl_certificates_are_present?).returns(false) connection = mock('Net::HTTP') connection.expects(:verify_mode=).with(OpenSSL::SSL::VERIFY_NONE) subject.setup_connection(connection) end end describe '#valid_peer?' do before :each do peer_certs = cert_chain_in_callback_order.map do |c| Puppet::SSL::Certificate.from_instance(c) end subject.instance_variable_set(:@peer_certs, peer_certs) end context 'when the peer presents a valid chain' do before :each do subject.stubs(:has_authz_peer_cert).returns(true) end it 'is true' do - subject.valid_peer?.should be_true + subject.valid_peer?.should be_truthy end end context 'when the peer presents an invalid chain' do before :each do subject.stubs(:has_authz_peer_cert).returns(false) end it 'is false' do - subject.valid_peer?.should be_false + subject.valid_peer?.should be_falsey end it 'makes a helpful error message available via #verify_errors' do subject.valid_peer? subject.verify_errors.should == [expected_authz_error_msg] end end end describe '#has_authz_peer_cert' do context 'when the Root CA is listed as authorized' do it 'returns true when the SSL cert is issued by the Master CA' do - subject.has_authz_peer_cert(cert_chain, [root_ca_cert]).should be_true + subject.has_authz_peer_cert(cert_chain, [root_ca_cert]).should be_truthy end it 'returns true when the SSL cert is issued by the Agent CA' do - subject.has_authz_peer_cert(cert_chain_agent_ca, [root_ca_cert]).should be_true + subject.has_authz_peer_cert(cert_chain_agent_ca, [root_ca_cert]).should be_truthy end end context 'when the Master CA is listed as authorized' do it 'returns false when the SSL cert is issued by the Master CA' do - subject.has_authz_peer_cert(cert_chain, [master_ca_cert]).should be_true + subject.has_authz_peer_cert(cert_chain, [master_ca_cert]).should be_truthy end it 'returns true when the SSL cert is issued by the Agent CA' do - subject.has_authz_peer_cert(cert_chain_agent_ca, [master_ca_cert]).should be_false + subject.has_authz_peer_cert(cert_chain_agent_ca, [master_ca_cert]).should be_falsey end end context 'when the Agent CA is listed as authorized' do it 'returns true when the SSL cert is issued by the Master CA' do - subject.has_authz_peer_cert(cert_chain, [agent_ca_cert]).should be_false + subject.has_authz_peer_cert(cert_chain, [agent_ca_cert]).should be_falsey end it 'returns true when the SSL cert is issued by the Agent CA' do - subject.has_authz_peer_cert(cert_chain_agent_ca, [agent_ca_cert]).should be_true + subject.has_authz_peer_cert(cert_chain_agent_ca, [agent_ca_cert]).should be_truthy end end end def root_ca <<-ROOT_CA -----BEGIN CERTIFICATE----- MIICYDCCAcmgAwIBAgIJALf2Pk2HvtBzMA0GCSqGSIb3DQEBBQUAMEkxEDAOBgNV BAMMB1Jvb3QgQ0ExGjAYBgNVBAsMEVNlcnZlciBPcGVyYXRpb25zMRkwFwYDVQQK DBBFeGFtcGxlIE9yZywgTExDMB4XDTEzMDMzMDA1NTA0OFoXDTMzMDMyNTA1NTA0 OFowSTEQMA4GA1UEAwwHUm9vdCBDQTEaMBgGA1UECwwRU2VydmVyIE9wZXJhdGlv bnMxGTAXBgNVBAoMEEV4YW1wbGUgT3JnLCBMTEMwgZ8wDQYJKoZIhvcNAQEBBQAD gY0AMIGJAoGBAMGSpafR4lboYOPfPJC1wVHHl0gD49ZVRjOlJ9jidEUjBdFXK6SA S1tecDv2G4tM1ANmfMKjZl0m+KaZ8O2oq0g6kxkq1Mg0eSNvlnEyehjmTLRzHC2i a0biH2wMtCLzfAoXDKy4GPlciBPE9mup5I8Kien5s91t92tc7K8AJ8oBAgMBAAGj UDBOMB0GA1UdDgQWBBQwTdZqjjXOIFK2hOM0bcOrnhQw2jAfBgNVHSMEGDAWgBQw TdZqjjXOIFK2hOM0bcOrnhQw2jAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUA A4GBACs8EZRrzgzAlcKC1Tz8GYlNHQg0XhpbEDm+p2mOV//PuDD190O+UBpWxo9Q rrkkx8En0wXQZJf6iH3hwewwHLOq5yXZKbJN+SmvJvRNL95Yhyy08Y9N65tJveE7 rPsNU/Tx19jHC87oXlmAePLI4IaUHXrWb7CRbY9TEcPdmj1R -----END CERTIFICATE----- ROOT_CA end def master_ca <<-MASTER_CA -----BEGIN CERTIFICATE----- MIICljCCAf+gAwIBAgIBAjANBgkqhkiG9w0BAQUFADBJMRAwDgYDVQQDDAdSb290 IENBMRowGAYDVQQLDBFTZXJ2ZXIgT3BlcmF0aW9uczEZMBcGA1UECgwQRXhhbXBs ZSBPcmcsIExMQzAeFw0xMzAzMzAwNTUwNDhaFw0zMzAzMjUwNTUwNDhaMH4xJDAi BgNVBAMTG0ludGVybWVkaWF0ZSBDQSAobWFzdGVyLWNhKTEfMB0GCSqGSIb3DQEJ ARYQdGVzdEBleGFtcGxlLm9yZzEZMBcGA1UEChMQRXhhbXBsZSBPcmcsIExMQzEa MBgGA1UECxMRU2VydmVyIE9wZXJhdGlvbnMwXDANBgkqhkiG9w0BAQEFAANLADBI AkEAvo/az3oR69SP92jGnUHMJLEyyD1Ui1BZ/rUABJcQTRQqn3RqtlfYePWZnUaZ srKbXRS4q0w5Vqf1kx5w3q5tIwIDAQABo4GcMIGZMHkGA1UdIwRyMHCAFDBN1mqO Nc4gUraE4zRtw6ueFDDaoU2kSzBJMRAwDgYDVQQDDAdSb290IENBMRowGAYDVQQL DBFTZXJ2ZXIgT3BlcmF0aW9uczEZMBcGA1UECgwQRXhhbXBsZSBPcmcsIExMQ4IJ ALf2Pk2HvtBzMA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMA0GCSqGSIb3 DQEBBQUAA4GBACRfa1YPS7RQUuhYovGgV0VYqxuATC7WwdIRihVh5FceSXKgSIbz BKmOBAy/KixEhpnHTbkpaJ0d9ITkvjMTmj3M5YMahKaQA5niVPckQPecMMd6jg9U l1k75xLLIcrlsDYo3999KOSSchH2K7bLT7TuQ2okdP6FHWmeWmudewlu -----END CERTIFICATE----- MASTER_CA end def agent_ca <<-AGENT_CA -----BEGIN CERTIFICATE----- MIIClTCCAf6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBJMRAwDgYDVQQDDAdSb290 IENBMRowGAYDVQQLDBFTZXJ2ZXIgT3BlcmF0aW9uczEZMBcGA1UECgwQRXhhbXBs ZSBPcmcsIExMQzAeFw0xMzAzMzAwNTUwNDhaFw0zMzAzMjUwNTUwNDhaMH0xIzAh BgNVBAMTGkludGVybWVkaWF0ZSBDQSAoYWdlbnQtY2EpMR8wHQYJKoZIhvcNAQkB FhB0ZXN0QGV4YW1wbGUub3JnMRkwFwYDVQQKExBFeGFtcGxlIE9yZywgTExDMRow GAYDVQQLExFTZXJ2ZXIgT3BlcmF0aW9uczBcMA0GCSqGSIb3DQEBAQUAA0sAMEgC QQDkEj/Msmi4hJImxP5+ocixMTHuYC1M1E2p4QcuzOkZYrfHf+5hJMcahfYhLiXU jHBredOXhgSisHh6CLSb/rKzAgMBAAGjgZwwgZkweQYDVR0jBHIwcIAUME3Wao41 ziBStoTjNG3Dq54UMNqhTaRLMEkxEDAOBgNVBAMMB1Jvb3QgQ0ExGjAYBgNVBAsM EVNlcnZlciBPcGVyYXRpb25zMRkwFwYDVQQKDBBFeGFtcGxlIE9yZywgTExDggkA t/Y+TYe+0HMwDwYDVR0TAQH/BAUwAwEB/zALBgNVHQ8EBAMCAQYwDQYJKoZIhvcN AQEFBQADgYEAujSj9rxIxJHEuuYXb15L30yxs9Tdvy4OCLiKdjvs9Z7gG8Pbutls ooCwyYAkmzKVs/8cYjZJnvJrPEW1gFwqX7Xknp85Cfrl+/pQEPYq5sZVa5BIm9tI 0EvlDax/Hd28jI6Bgq5fsTECNl9GDGknCy7vwRZem0h+hI56lzR3pYE= -----END CERTIFICATE----- AGENT_CA end # Signed by the master CA (Good) def master_issued_by_master_ca <<-GOOD_SSL_CERT -----BEGIN CERTIFICATE----- MIICZzCCAhGgAwIBAgIBATANBgkqhkiG9w0BAQUFADB+MSQwIgYDVQQDExtJbnRl cm1lZGlhdGUgQ0EgKG1hc3Rlci1jYSkxHzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhh bXBsZS5vcmcxGTAXBgNVBAoTEEV4YW1wbGUgT3JnLCBMTEMxGjAYBgNVBAsTEVNl cnZlciBPcGVyYXRpb25zMB4XDTEzMDMzMDA1NTA0OFoXDTMzMDMyNTA1NTA0OFow HjEcMBoGA1UEAwwTbWFzdGVyMS5leGFtcGxlLm9yZzBcMA0GCSqGSIb3DQEBAQUA A0sAMEgCQQDACW8fryVZH0dC7vYUASonVBKYcILnKN2O9QX7RenZGN1TWek9LQxr yQFDyp7WJ8jUw6nENGniLU8J+QSSxryjAgMBAAGjgdkwgdYwWwYDVR0jBFQwUqFN pEswSTEQMA4GA1UEAwwHUm9vdCBDQTEaMBgGA1UECwwRU2VydmVyIE9wZXJhdGlv bnMxGTAXBgNVBAoMEEV4YW1wbGUgT3JnLCBMTEOCAQIwDAYDVR0TAQH/BAIwADAL BgNVHQ8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMD0GA1Ud EQQ2MDSCE21hc3RlcjEuZXhhbXBsZS5vcmeCB21hc3RlcjGCBnB1cHBldIIMcHVw cGV0bWFzdGVyMA0GCSqGSIb3DQEBBQUAA0EAo8PvgLrah6jQVs6YCBxOTn13PDip fVbcRsFd0dtIr00N61bCqr6Fa0aRwy424gh6bVJTNmk2zoaH7r025dZRhw== -----END CERTIFICATE----- GOOD_SSL_CERT end # Signed by the agent CA, not the master CA (Rogue) def master_issued_by_agent_ca <<-BAD_SSL_CERT -----BEGIN CERTIFICATE----- MIICZjCCAhCgAwIBAgIBBDANBgkqhkiG9w0BAQUFADB9MSMwIQYDVQQDExpJbnRl cm1lZGlhdGUgQ0EgKGFnZW50LWNhKTEfMB0GCSqGSIb3DQEJARYQdGVzdEBleGFt cGxlLm9yZzEZMBcGA1UEChMQRXhhbXBsZSBPcmcsIExMQzEaMBgGA1UECxMRU2Vy dmVyIE9wZXJhdGlvbnMwHhcNMTMwMzMwMDU1MDQ4WhcNMzMwMzI1MDU1MDQ4WjAe MRwwGgYDVQQDDBNtYXN0ZXIxLmV4YW1wbGUub3JnMFwwDQYJKoZIhvcNAQEBBQAD SwAwSAJBAPnCDnryLLXWepGLqsdBWlytfeakE/yijM8GlE/yT0SbpJInIhJR1N1A 0RskriHrxTU5qQEhd0RIja7K5o4NYksCAwEAAaOB2TCB1jBbBgNVHSMEVDBSoU2k SzBJMRAwDgYDVQQDDAdSb290IENBMRowGAYDVQQLDBFTZXJ2ZXIgT3BlcmF0aW9u czEZMBcGA1UECgwQRXhhbXBsZSBPcmcsIExMQ4IBATAMBgNVHRMBAf8EAjAAMAsG A1UdDwQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwPQYDVR0R BDYwNIITbWFzdGVyMS5leGFtcGxlLm9yZ4IHbWFzdGVyMYIGcHVwcGV0ggxwdXBw ZXRtYXN0ZXIwDQYJKoZIhvcNAQEFBQADQQA841IzHLlnn4RIJ0/BOZ/16iWC1dNr jV9bELC5OxeMNSsVXbFNeTHwbHEYjDg5dQ6eUkxPdBSMWBeQwe2Mw+xG -----END CERTIFICATE----- BAD_SSL_CERT end def cert_chain [ master_issued_by_master_ca, master_ca, root_ca ].map do |pem| OpenSSL::X509::Certificate.new(pem) end end def cert_chain_agent_ca [ master_issued_by_agent_ca, agent_ca, root_ca ].map do |pem| OpenSSL::X509::Certificate.new(pem) end end def cert_chain_in_callback_order cert_chain.reverse end let :authz_error_prefix do "The server presented a SSL certificate chain which does not include a CA listed in the ssl_client_ca_auth file. " end let :expected_authz_error_msg do authz_ca_certs = ssl_configuration.ca_auth_certificates msg = authz_error_prefix msg << "Authorized Issuers: #{authz_ca_certs.collect {|c| c.subject}.join(', ')} " msg << "Peer Chain: #{cert_chain.collect {|c| c.subject}.join(' => ')}" msg end let :root_ca_cert do OpenSSL::X509::Certificate.new(root_ca) end let :master_ca_cert do OpenSSL::X509::Certificate.new(master_ca) end let :agent_ca_cert do OpenSSL::X509::Certificate.new(agent_ca) end end diff --git a/spec/unit/transaction/report_spec.rb b/spec/unit/transaction/report_spec.rb index a349374e6..d456608e3 100755 --- a/spec/unit/transaction/report_spec.rb +++ b/spec/unit/transaction/report_spec.rb @@ -1,483 +1,483 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet' require 'puppet/transaction/report' require 'matchers/json' describe Puppet::Transaction::Report do include JSONMatchers include PuppetSpec::Files before do Puppet::Util::Storage.stubs(:store) end it "should set its host name to the node_name_value" do Puppet[:node_name_value] = 'mynode' Puppet::Transaction::Report.new("apply").host.should == "mynode" end it "should return its host name as its name" do r = Puppet::Transaction::Report.new("apply") r.name.should == r.host end it "should create an initialization timestamp" do Time.expects(:now).returns "mytime" Puppet::Transaction::Report.new("apply").time.should == "mytime" end it "should take a 'kind' as an argument" do Puppet::Transaction::Report.new("inspect").kind.should == "inspect" end it "should take a 'configuration_version' as an argument" do Puppet::Transaction::Report.new("inspect", "some configuration version", "some environment").configuration_version.should == "some configuration version" end it "should take a 'transaction_uuid' as an argument" do Puppet::Transaction::Report.new("inspect", "some configuration version", "some environment", "some transaction uuid").transaction_uuid.should == "some transaction uuid" end it "should be able to set configuration_version" do report = Puppet::Transaction::Report.new("inspect") report.configuration_version = "some version" report.configuration_version.should == "some version" end it "should be able to set transaction_uuid" do report = Puppet::Transaction::Report.new("inspect") report.transaction_uuid = "some transaction uuid" report.transaction_uuid.should == "some transaction uuid" end it "should take 'environment' as an argument" do Puppet::Transaction::Report.new("inspect", "some configuration version", "some environment").environment.should == "some environment" end it "should be able to set environment" do report = Puppet::Transaction::Report.new("inspect") report.environment = "some environment" report.environment.should == "some environment" end it "should not include whits" do Puppet::FileBucket::File.indirection.stubs(:save) filename = tmpfile('whit_test') file = Puppet::Type.type(:file).new(:path => filename) catalog = Puppet::Resource::Catalog.new catalog.add_resource(file) report = Puppet::Transaction::Report.new("apply") catalog.apply(:report => report) report.finalize_report - report.resource_statuses.values.any? {|res| res.resource_type =~ /whit/i}.should be_false - report.metrics['time'].values.any? {|metric| metric.first =~ /whit/i}.should be_false + report.resource_statuses.values.any? {|res| res.resource_type =~ /whit/i}.should be_falsey + report.metrics['time'].values.any? {|metric| metric.first =~ /whit/i}.should be_falsey end describe "when accepting logs" do before do @report = Puppet::Transaction::Report.new("apply") end it "should add new logs to the log list" do @report << "log" @report.logs[-1].should == "log" end it "should return self" do r = @report << "log" r.should equal(@report) end end describe "#as_logging_destination" do it "makes the report collect logs during the block " do log_string = 'Hello test report!' report = Puppet::Transaction::Report.new('test') report.as_logging_destination do Puppet.err(log_string) end expect(report.logs.collect(&:message)).to include(log_string) end end describe "when accepting resource statuses" do before do @report = Puppet::Transaction::Report.new("apply") end it "should add each status to its status list" do status = stub 'status', :resource => "foo" @report.add_resource_status status @report.resource_statuses["foo"].should equal(status) end end describe "when using the indirector" do it "should redirect :save to the indirection" do Facter.stubs(:value).returns("eh") @indirection = stub 'indirection', :name => :report Puppet::Transaction::Report.stubs(:indirection).returns(@indirection) report = Puppet::Transaction::Report.new("apply") @indirection.expects(:save) Puppet::Transaction::Report.indirection.save(report) end it "should default to the 'processor' terminus" do Puppet::Transaction::Report.indirection.terminus_class.should == :processor end it "should delegate its name attribute to its host method" do report = Puppet::Transaction::Report.new("apply") report.expects(:host).returns "me" report.name.should == "me" end end describe "when computing exit status" do it "should produce 2 if changes are present" do report = Puppet::Transaction::Report.new("apply") report.add_metric("changes", {"total" => 1}) report.add_metric("resources", {"failed" => 0}) report.exit_status.should == 2 end it "should produce 4 if failures are present" do report = Puppet::Transaction::Report.new("apply") report.add_metric("changes", {"total" => 0}) report.add_metric("resources", {"failed" => 1}) report.exit_status.should == 4 end it "should produce 4 if failures to restart are present" do report = Puppet::Transaction::Report.new("apply") report.add_metric("changes", {"total" => 0}) report.add_metric("resources", {"failed" => 0}) report.add_metric("resources", {"failed_to_restart" => 1}) report.exit_status.should == 4 end it "should produce 6 if both changes and failures are present" do report = Puppet::Transaction::Report.new("apply") report.add_metric("changes", {"total" => 1}) report.add_metric("resources", {"failed" => 1}) report.exit_status.should == 6 end end describe "before finalizing the report" do it "should have a status of 'failed'" do report = Puppet::Transaction::Report.new("apply") report.status.should == 'failed' end end describe "when finalizing the report" do before do @report = Puppet::Transaction::Report.new("apply") end def metric(name, value) if metric = @report.metrics[name.to_s] metric[value] else nil end end def add_statuses(count, type = :file) count.times do |i| status = Puppet::Resource::Status.new(Puppet::Type.type(type).new(:title => make_absolute("/my/path#{i}"))) yield status if block_given? @report.add_resource_status status end end [:time, :resources, :changes, :events].each do |type| it "should add #{type} metrics" do @report.finalize_report @report.metrics[type.to_s].should be_instance_of(Puppet::Transaction::Metric) end end describe "for resources" do it "should provide the total number of resources" do add_statuses(3) @report.finalize_report metric(:resources, "total").should == 3 end Puppet::Resource::Status::STATES.each do |state| it "should provide the number of #{state} resources as determined by the status objects" do add_statuses(3) { |status| status.send(state.to_s + "=", true) } @report.finalize_report metric(:resources, state.to_s).should == 3 end it "should provide 0 for states not in status" do @report.finalize_report metric(:resources, state.to_s).should == 0 end end it "should mark the report as 'failed' if there are failing resources" do add_statuses(1) { |status| status.failed = true } @report.finalize_report @report.status.should == 'failed' end end describe "for changes" do it "should provide the number of changes from the resource statuses and mark the report as 'changed'" do add_statuses(3) { |status| 3.times { status << Puppet::Transaction::Event.new(:status => 'success') } } @report.finalize_report metric(:changes, "total").should == 9 @report.status.should == 'changed' end it "should provide a total even if there are no changes, and mark the report as 'unchanged'" do @report.finalize_report metric(:changes, "total").should == 0 @report.status.should == 'unchanged' end end describe "for times" do it "should provide the total amount of time for each resource type" do add_statuses(3, :file) do |status| status.evaluation_time = 1 end add_statuses(3, :exec) do |status| status.evaluation_time = 2 end add_statuses(3, :tidy) do |status| status.evaluation_time = 3 end @report.finalize_report metric(:time, "file").should == 3 metric(:time, "exec").should == 6 metric(:time, "tidy").should == 9 end it "should add any provided times from external sources" do @report.add_times :foobar, 50 @report.finalize_report metric(:time, "foobar").should == 50 end it "should have a total time" do add_statuses(3, :file) do |status| status.evaluation_time = 1.25 end @report.add_times :config_retrieval, 0.5 @report.finalize_report metric(:time, "total").should == 4.25 end end describe "for events" do it "should provide the total number of events" do add_statuses(3) do |status| 3.times { |i| status.add_event(Puppet::Transaction::Event.new :status => 'success') } end @report.finalize_report metric(:events, "total").should == 9 end it "should provide the total even if there are no events" do @report.finalize_report metric(:events, "total").should == 0 end Puppet::Transaction::Event::EVENT_STATUSES.each do |status_name| it "should provide the number of #{status_name} events" do add_statuses(3) do |status| 3.times do |i| event = Puppet::Transaction::Event.new event.status = status_name status.add_event(event) end end @report.finalize_report metric(:events, status_name).should == 9 end end end end describe "when producing a summary" do before do resource = Puppet::Type.type(:notify).new(:name => "testing") catalog = Puppet::Resource::Catalog.new catalog.add_resource resource catalog.version = 1234567 trans = catalog.apply @report = trans.report @report.finalize_report end %w{changes time resources events version}.each do |main| it "should include the key #{main} in the raw summary hash" do @report.raw_summary.should be_key main end end it "should include the last run time in the raw summary hash" do Time.stubs(:now).returns(Time.utc(2010,11,10,12,0,24)) @report.raw_summary["time"]["last_run"].should == 1289390424 end it "should include all resource statuses" do resources_report = @report.raw_summary["resources"] Puppet::Resource::Status::STATES.each do |state| resources_report.should be_include(state.to_s) end end %w{total failure success}.each do |r| it "should include event #{r}" do events_report = @report.raw_summary["events"] events_report.should be_include(r) end end it "should include config version" do @report.raw_summary["version"]["config"].should == 1234567 end it "should include puppet version" do @report.raw_summary["version"]["puppet"].should == Puppet.version end %w{Changes Total Resources Time Events}.each do |main| it "should include information on #{main} in the textual summary" do @report.summary.should be_include(main) end end end describe "when outputting yaml" do it "should not include @external_times" do report = Puppet::Transaction::Report.new('apply') report.add_times('config_retrieval', 1.0) report.to_yaml_properties.should_not include('@external_times') end end it "defaults to serializing to pson" do expect(Puppet::Transaction::Report.default_format).to eq(:pson) end it "supports both yaml and pson" do expect(Puppet::Transaction::Report.supported_formats).to eq([:pson, :yaml]) end it "can make a round trip through pson" do report = generate_report tripped = Puppet::Transaction::Report.convert_from(:pson, report.render) expect_equivalent_reports(tripped, report) end it "generates pson which validates against the report schema" do report = generate_report expect(report.render).to validate_against('api/schemas/report.json') end it "generates pson for error report which validates against the report schema" do error_report = generate_report_with_error expect(error_report.render).to validate_against('api/schemas/report.json') end def expect_equivalent_reports(tripped, report) tripped.host.should == report.host tripped.time.to_i.should == report.time.to_i tripped.configuration_version.should == report.configuration_version tripped.transaction_uuid.should == report.transaction_uuid tripped.report_format.should == report.report_format tripped.puppet_version.should == report.puppet_version tripped.kind.should == report.kind tripped.status.should == report.status tripped.environment.should == report.environment logs_as_strings(tripped).should == logs_as_strings(report) metrics_as_hashes(tripped).should == metrics_as_hashes(report) expect_equivalent_resource_statuses(tripped.resource_statuses, report.resource_statuses) end def logs_as_strings(report) report.logs.map(&:to_report) end def metrics_as_hashes(report) Hash[*report.metrics.collect do |name, m| [name, { :name => m.name, :label => m.label, :value => m.value }] end.flatten] end def expect_equivalent_resource_statuses(tripped, report) tripped.keys.sort.should == report.keys.sort tripped.each_pair do |name, status| expected = report[name] status.title.should == expected.title status.file.should == expected.file status.line.should == expected.line status.resource.should == expected.resource status.resource_type.should == expected.resource_type status.containment_path.should == expected.containment_path status.evaluation_time.should == expected.evaluation_time status.tags.should == expected.tags status.time.to_i.should == expected.time.to_i status.failed.should == expected.failed status.changed.should == expected.changed status.out_of_sync.should == expected.out_of_sync status.skipped.should == expected.skipped status.change_count.should == expected.change_count status.out_of_sync_count.should == expected.out_of_sync_count status.events.should == expected.events end end def generate_report status = Puppet::Resource::Status.new(Puppet::Type.type(:notify).new(:title => "a resource")) status.changed = true report = Puppet::Transaction::Report.new('apply', 1357986, 'test_environment', "df34516e-4050-402d-a166-05b03b940749") report << Puppet::Util::Log.new(:level => :warning, :message => "log message") report.add_times("timing", 4) report.add_resource_status(status) report.finalize_report report end def generate_report_with_error status = Puppet::Resource::Status.new(Puppet::Type.type(:notify).new(:title => "a resource")) status.changed = true status.failed_because("bad stuff happened") report = Puppet::Transaction::Report.new('apply', 1357986, 'test_environment', "df34516e-4050-402d-a166-05b03b940749") report << Puppet::Util::Log.new(:level => :warning, :message => "log message") report.add_times("timing", 4) report.add_resource_status(status) report.finalize_report report end end diff --git a/spec/unit/transaction_spec.rb b/spec/unit/transaction_spec.rb index e608a5d6b..3de64750a 100755 --- a/spec/unit/transaction_spec.rb +++ b/spec/unit/transaction_spec.rb @@ -1,736 +1,736 @@ #! /usr/bin/env ruby require 'spec_helper' require 'matchers/include_in_order' require 'puppet_spec/compiler' require 'puppet/transaction' require 'fileutils' describe Puppet::Transaction do include PuppetSpec::Files include PuppetSpec::Compiler def catalog_with_resource(resource) catalog = Puppet::Resource::Catalog.new catalog.add_resource(resource) catalog end def transaction_with_resource(resource) transaction = Puppet::Transaction.new(catalog_with_resource(resource), nil, Puppet::Graph::RandomPrioritizer.new) transaction end before do @basepath = make_absolute("/what/ever") @transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new, nil, Puppet::Graph::RandomPrioritizer.new) end it "should be able to look resource status up by resource reference" do resource = Puppet::Type.type(:notify).new :title => "foobar" transaction = transaction_with_resource(resource) transaction.evaluate transaction.resource_status(resource.to_s).should be_changed end # This will basically only ever be used during testing. it "should automatically create resource statuses if asked for a non-existent status" do resource = Puppet::Type.type(:notify).new :title => "foobar" transaction = transaction_with_resource(resource) transaction.resource_status(resource).should be_instance_of(Puppet::Resource::Status) end it "should add provided resource statuses to its report" do resource = Puppet::Type.type(:notify).new :title => "foobar" transaction = transaction_with_resource(resource) transaction.evaluate status = transaction.resource_status(resource) transaction.report.resource_statuses[resource.to_s].should equal(status) end it "should not consider there to be failed resources if no statuses are marked failed" do resource = Puppet::Type.type(:notify).new :title => "foobar" transaction = transaction_with_resource(resource) transaction.evaluate transaction.should_not be_any_failed end it "should use the provided report object" do report = Puppet::Transaction::Report.new("apply") transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new, report, nil) transaction.report.should == report end it "should create a report if none is provided" do transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new, nil, nil) transaction.report.should be_kind_of Puppet::Transaction::Report end describe "when initializing" do it "should create an event manager" do transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new, nil, nil) transaction.event_manager.should be_instance_of(Puppet::Transaction::EventManager) transaction.event_manager.transaction.should equal(transaction) end it "should create a resource harness" do transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new, nil, nil) transaction.resource_harness.should be_instance_of(Puppet::Transaction::ResourceHarness) transaction.resource_harness.transaction.should equal(transaction) end it "should set retrieval time on the report" do catalog = Puppet::Resource::Catalog.new report = Puppet::Transaction::Report.new("apply") catalog.retrieval_duration = 5 report.expects(:add_times).with(:config_retrieval, 5) transaction = Puppet::Transaction.new(catalog, report, nil) end end describe "when evaluating a resource" do let(:resource) { Puppet::Type.type(:file).new :path => @basepath } it "should process events" do transaction = transaction_with_resource(resource) transaction.expects(:skip?).with(resource).returns false transaction.event_manager.expects(:process_events).with(resource) transaction.evaluate end describe "and the resource should be skipped" do it "should mark the resource's status as skipped" do transaction = transaction_with_resource(resource) transaction.expects(:skip?).with(resource).returns true transaction.evaluate transaction.resource_status(resource).should be_skipped end it "does not process any scheduled events" do transaction = transaction_with_resource(resource) transaction.expects(:skip?).with(resource).returns true transaction.event_manager.expects(:process_events).with(resource).never transaction.evaluate end it "dequeues all events scheduled on that resource" do transaction = transaction_with_resource(resource) transaction.expects(:skip?).with(resource).returns true transaction.event_manager.expects(:dequeue_all_events_for_resource).with(resource) transaction.evaluate end end end describe "when applying a resource" do before do @catalog = Puppet::Resource::Catalog.new @resource = Puppet::Type.type(:file).new :path => @basepath @catalog.add_resource(@resource) @status = Puppet::Resource::Status.new(@resource) @transaction = Puppet::Transaction.new(@catalog, nil, Puppet::Graph::RandomPrioritizer.new) @transaction.event_manager.stubs(:queue_events) end it "should use its resource harness to apply the resource" do @transaction.resource_harness.expects(:evaluate).with(@resource) @transaction.evaluate end it "should add the resulting resource status to its status list" do @transaction.resource_harness.stubs(:evaluate).returns(@status) @transaction.evaluate @transaction.resource_status(@resource).should be_instance_of(Puppet::Resource::Status) end it "should queue any events added to the resource status" do @transaction.resource_harness.stubs(:evaluate).returns(@status) @status.expects(:events).returns %w{a b} @transaction.event_manager.expects(:queue_events).with(@resource, ["a", "b"]) @transaction.evaluate end it "should log and skip any resources that cannot be applied" do @resource.expects(:properties).raises ArgumentError @transaction.evaluate @transaction.report.resource_statuses[@resource.to_s].should be_failed end it "should report any_failed if any resources failed" do @resource.expects(:properties).raises ArgumentError @transaction.evaluate expect(@transaction).to be_any_failed end end describe "#unblock" do let(:graph) { @transaction.relationship_graph } let(:resource) { Puppet::Type.type(:notify).new(:name => 'foo') } it "should calculate the number of blockers if it's not known" do graph.add_vertex(resource) 3.times do |i| other = Puppet::Type.type(:notify).new(:name => i.to_s) graph.add_vertex(other) graph.add_edge(other, resource) end graph.unblock(resource) graph.blockers[resource].should == 2 end it "should decrement the number of blockers if there are any" do graph.blockers[resource] = 40 graph.unblock(resource) graph.blockers[resource].should == 39 end it "should warn if there are no blockers" do vertex = stub('vertex') vertex.expects(:warning).with "appears to have a negative number of dependencies" graph.blockers[vertex] = 0 graph.unblock(vertex) end it "should return true if the resource is now unblocked" do graph.blockers[resource] = 1 graph.unblock(resource).should == true end it "should return false if the resource is still blocked" do graph.blockers[resource] = 2 graph.unblock(resource).should == false end end describe "when traversing" do let(:path) { tmpdir('eval_generate') } let(:resource) { Puppet::Type.type(:file).new(:path => path, :recurse => true) } before :each do @transaction.catalog.add_resource(resource) end it "should yield the resource even if eval_generate is called" do Puppet::Transaction::AdditionalResourceGenerator.any_instance.expects(:eval_generate).with(resource).returns true yielded = false @transaction.evaluate do |res| yielded = true if res == resource end yielded.should == true end it "should prefetch the provider if necessary" do @transaction.expects(:prefetch_if_necessary).with(resource) @transaction.evaluate {} end it "traverses independent resources before dependent resources" do dependent = Puppet::Type.type(:notify).new(:name => "hello", :require => resource) @transaction.catalog.add_resource(dependent) seen = [] @transaction.evaluate do |res| seen << res end expect(seen).to include_in_order(resource, dependent) end it "traverses completely independent resources in the order they appear in the catalog" do independent = Puppet::Type.type(:notify).new(:name => "hello", :require => resource) @transaction.catalog.add_resource(independent) seen = [] @transaction.evaluate do |res| seen << res end expect(seen).to include_in_order(resource, independent) end it "should fail unsuitable resources and go on if it gets blocked" do dependent = Puppet::Type.type(:notify).new(:name => "hello", :require => resource) @transaction.catalog.add_resource(dependent) resource.stubs(:suitable?).returns false evaluated = [] @transaction.evaluate do |res| evaluated << res end # We should have gone on to evaluate the children evaluated.should == [dependent] @transaction.resource_status(resource).should be_failed end end describe "when generating resources before traversal" do let(:catalog) { Puppet::Resource::Catalog.new } let(:transaction) { Puppet::Transaction.new(catalog, nil, Puppet::Graph::RandomPrioritizer.new) } let(:generator) { Puppet::Type.type(:notify).new :title => "generator" } let(:generated) do %w[a b c].map { |name| Puppet::Type.type(:notify).new(:name => name) } end before :each do catalog.add_resource generator generator.stubs(:generate).returns generated # avoid crude failures because of nil resources that result # from implicit containment and lacking containers catalog.stubs(:container_of).returns generator end it "should call 'generate' on all created resources" do generated.each { |res| res.expects(:generate) } transaction.evaluate end it "should finish all resources" do generated.each { |res| res.expects(:finish) } transaction.evaluate end it "should copy all tags to the newly generated resources" do generator.tag('one', 'two') transaction.evaluate generated.each do |res| res.must be_tagged(*generator.tags) end end end describe "when performing pre-run checks" do let(:resource) { Puppet::Type.type(:notify).new(:title => "spec") } let(:transaction) { transaction_with_resource(resource) } let(:spec_exception) { 'spec-exception' } it "should invoke each resource's hook and apply the catalog after no failures" do resource.expects(:pre_run_check) transaction.evaluate end it "should abort the transaction on failure" do resource.expects(:pre_run_check).raises(Puppet::Error, spec_exception) expect { transaction.evaluate }.to raise_error(Puppet::Error, /Some pre-run checks failed/) end it "should log the resource-specific exception" do resource.expects(:pre_run_check).raises(Puppet::Error, spec_exception) resource.expects(:log_exception).with(responds_with(:message, spec_exception)) expect { transaction.evaluate }.to raise_error(Puppet::Error) end end describe "when skipping a resource" do before :each do @resource = Puppet::Type.type(:notify).new :name => "foo" @catalog = Puppet::Resource::Catalog.new @resource.catalog = @catalog @transaction = Puppet::Transaction.new(@catalog, nil, nil) end it "should skip resource with missing tags" do @transaction.stubs(:missing_tags?).returns(true) @transaction.should be_skip(@resource) end it "should skip unscheduled resources" do @transaction.stubs(:scheduled?).returns(false) @transaction.should be_skip(@resource) end it "should skip resources with failed dependencies" do @transaction.stubs(:failed_dependencies?).returns(true) @transaction.should be_skip(@resource) end it "should skip virtual resource" do @resource.stubs(:virtual?).returns true @transaction.should be_skip(@resource) end it "should skip device only resouce on normal host" do @resource.stubs(:appliable_to_host?).returns false @resource.stubs(:appliable_to_device?).returns true @transaction.for_network_device = false @transaction.should be_skip(@resource) end it "should not skip device only resouce on remote device" do @resource.stubs(:appliable_to_host?).returns false @resource.stubs(:appliable_to_device?).returns true @transaction.for_network_device = true @transaction.should_not be_skip(@resource) end it "should skip host resouce on device" do @resource.stubs(:appliable_to_host?).returns true @resource.stubs(:appliable_to_device?).returns false @transaction.for_network_device = true @transaction.should be_skip(@resource) end it "should not skip resouce available on both device and host when on device" do @resource.stubs(:appliable_to_host?).returns true @resource.stubs(:appliable_to_device?).returns true @transaction.for_network_device = true @transaction.should_not be_skip(@resource) end it "should not skip resouce available on both device and host when on host" do @resource.stubs(:appliable_to_host?).returns true @resource.stubs(:appliable_to_device?).returns true @transaction.for_network_device = false @transaction.should_not be_skip(@resource) end end describe "when determining if tags are missing" do before :each do @resource = Puppet::Type.type(:notify).new :name => "foo" @catalog = Puppet::Resource::Catalog.new @resource.catalog = @catalog @transaction = Puppet::Transaction.new(@catalog, nil, nil) @transaction.stubs(:ignore_tags?).returns false end it "should not be missing tags if tags are being ignored" do @transaction.expects(:ignore_tags?).returns true @resource.expects(:tagged?).never @transaction.should_not be_missing_tags(@resource) end it "should not be missing tags if the transaction tags are empty" do @transaction.tags = [] @resource.expects(:tagged?).never @transaction.should_not be_missing_tags(@resource) end it "should otherwise let the resource determine if it is missing tags" do tags = ['one', 'two'] @transaction.tags = tags @transaction.should be_missing_tags(@resource) end end describe "when determining if a resource should be scheduled" do before :each do @resource = Puppet::Type.type(:notify).new :name => "foo" @catalog = Puppet::Resource::Catalog.new @catalog.add_resource(@resource) @transaction = Puppet::Transaction.new(@catalog, nil, Puppet::Graph::RandomPrioritizer.new) end it "should always schedule resources if 'ignoreschedules' is set" do @transaction.ignoreschedules = true @transaction.resource_harness.expects(:scheduled?).never @transaction.evaluate @transaction.resource_status(@resource).should be_changed end it "should let the resource harness determine whether the resource should be scheduled" do @transaction.resource_harness.expects(:scheduled?).with(@resource).returns "feh" @transaction.evaluate end end describe "when prefetching" do let(:catalog) { Puppet::Resource::Catalog.new } let(:transaction) { Puppet::Transaction.new(catalog, nil, nil) } let(:resource) { Puppet::Type.type(:sshkey).new :title => "foo", :name => "bar", :type => :dsa, :key => "eh", :provider => :parsed } let(:resource2) { Puppet::Type.type(:package).new :title => "blah", :provider => "apt" } before :each do catalog.add_resource resource catalog.add_resource resource2 end it "should match resources by name, not title" do resource.provider.class.expects(:prefetch).with("bar" => resource) transaction.prefetch_if_necessary(resource) end it "should not prefetch a provider which has already been prefetched" do transaction.prefetched_providers[:sshkey][:parsed] = true resource.provider.class.expects(:prefetch).never transaction.prefetch_if_necessary(resource) end it "should mark the provider prefetched" do resource.provider.class.stubs(:prefetch) transaction.prefetch_if_necessary(resource) - transaction.prefetched_providers[:sshkey][:parsed].should be_true + transaction.prefetched_providers[:sshkey][:parsed].should be_truthy end it "should prefetch resources without a provider if prefetching the default provider" do other = Puppet::Type.type(:sshkey).new :name => "other" other.instance_variable_set(:@provider, nil) catalog.add_resource other resource.provider.class.expects(:prefetch).with('bar' => resource, 'other' => other) transaction.prefetch_if_necessary(resource) end end describe "during teardown" do let(:catalog) { Puppet::Resource::Catalog.new } let(:transaction) do Puppet::Transaction.new(catalog, nil, Puppet::Graph::RandomPrioritizer.new) end let(:teardown_type) do Puppet::Type.newtype(:teardown_test) do newparam(:name) {} end end before :each do teardown_type.provide(:teardown_provider) do class << self attr_reader :result def post_resource_eval @result = 'passed' end end end end it "should call ::post_resource_eval on provider classes that support it" do resource = teardown_type.new(:title => "foo", :provider => :teardown_provider) transaction = transaction_with_resource(resource) transaction.evaluate expect(resource.provider.class.result).to eq('passed') end it "should call ::post_resource_eval even if other providers' ::post_resource_eval fails" do teardown_type.provide(:always_fails) do class << self attr_reader :result def post_resource_eval @result = 'failed' raise Puppet::Error, "This provider always fails" end end end good_resource = teardown_type.new(:title => "bloo", :provider => :teardown_provider) bad_resource = teardown_type.new(:title => "blob", :provider => :always_fails) catalog.add_resource(bad_resource) catalog.add_resource(good_resource) transaction.evaluate expect(good_resource.provider.class.result).to eq('passed') expect(bad_resource.provider.class.result).to eq('failed') end it "should call ::post_resource_eval even if one of the resources fails" do resource = teardown_type.new(:title => "foo", :provider => :teardown_provider) resource.stubs(:retrieve_resource).raises catalog.add_resource resource resource.provider.class.expects(:post_resource_eval) transaction.evaluate end end describe 'when checking application run state' do before do @catalog = Puppet::Resource::Catalog.new @transaction = Puppet::Transaction.new(@catalog, nil, Puppet::Graph::RandomPrioritizer.new) end context "when stop is requested" do before :each do Puppet::Application.stubs(:stop_requested?).returns(true) end it 'should return true for :stop_processing?' do @transaction.should be_stop_processing end it 'always evaluates non-host_config catalogs' do @catalog.host_config = false @transaction.should_not be_stop_processing end end it 'should return false for :stop_processing? if Puppet::Application.stop_requested? is false' do Puppet::Application.stubs(:stop_requested?).returns(false) - @transaction.stop_processing?.should be_false + @transaction.stop_processing?.should be_falsey end describe 'within an evaluate call' do before do @resource = Puppet::Type.type(:notify).new :title => "foobar" @catalog.add_resource @resource @transaction.stubs(:add_dynamically_generated_resources) end it 'should stop processing if :stop_processing? is true' do @transaction.stubs(:stop_processing?).returns(true) @transaction.expects(:eval_resource).never @transaction.evaluate end it 'should continue processing if :stop_processing? is false' do @transaction.stubs(:stop_processing?).returns(false) @transaction.expects(:eval_resource).returns(nil) @transaction.evaluate end end end it "errors with a dependency cycle for a resource that requires itself" do expect do apply_compiled_manifest(<<-MANIFEST) notify { cycle: require => Notify[cycle] } MANIFEST end.to raise_error(Puppet::Error, /Found 1 dependency cycle:.*\(Notify\[cycle\] => Notify\[cycle\]\)/m) end it "errors with a dependency cycle for a self-requiring resource also required by another resource" do expect do apply_compiled_manifest(<<-MANIFEST) notify { cycle: require => Notify[cycle] } notify { other: require => Notify[cycle] } MANIFEST end.to raise_error(Puppet::Error, /Found 1 dependency cycle:.*\(Notify\[cycle\] => Notify\[cycle\]\)/m) end it "errors with a dependency cycle for a resource that requires itself and another resource" do expect do apply_compiled_manifest(<<-MANIFEST) notify { cycle: require => [Notify[other], Notify[cycle]] } notify { other: } MANIFEST end.to raise_error(Puppet::Error, /Found 1 dependency cycle:.*\(Notify\[cycle\] => Notify\[cycle\]\)/m) end it "errors with a dependency cycle for a resource that is later modified to require itself" do expect do apply_compiled_manifest(<<-MANIFEST) notify { cycle: } Notify <| title == 'cycle' |> { require => Notify[cycle] } MANIFEST end.to raise_error(Puppet::Error, /Found 1 dependency cycle:.*\(Notify\[cycle\] => Notify\[cycle\]\)/m) end it "reports a changed resource with a successful run" do transaction = apply_compiled_manifest("notify { one: }") transaction.report.status.should == 'changed' transaction.report.resource_statuses['Notify[one]'].should be_changed end describe "when interrupted" do it "marks unprocessed resources as skipped" do Puppet::Application.stop! transaction = apply_compiled_manifest(<<-MANIFEST) notify { a: } -> notify { b: } MANIFEST transaction.report.resource_statuses['Notify[a]'].should be_skipped transaction.report.resource_statuses['Notify[b]'].should be_skipped end end end describe Puppet::Transaction, " when determining tags" do before do @config = Puppet::Resource::Catalog.new @transaction = Puppet::Transaction.new(@config, nil, nil) end it "should default to the tags specified in the :tags setting" do Puppet[:tags] = "one" @transaction.should be_tagged("one") end it "should split tags based on ','" do Puppet[:tags] = "one,two" @transaction.should be_tagged("one") @transaction.should be_tagged("two") end it "should use any tags set after creation" do Puppet[:tags] = "" @transaction.tags = %w{one two} @transaction.should be_tagged("one") @transaction.should be_tagged("two") end it "should always convert assigned tags to an array" do @transaction.tags = "one::two" @transaction.should be_tagged("one::two") end it "should accept a comma-delimited string" do @transaction.tags = "one, two" @transaction.should be_tagged("one") @transaction.should be_tagged("two") end it "should accept an empty string" do @transaction.tags = "one, two" @transaction.should be_tagged("one") @transaction.tags = "" @transaction.should_not be_tagged("one") end end diff --git a/spec/unit/type/file/ensure_spec.rb b/spec/unit/type/file/ensure_spec.rb index 0198b06d3..dc208fc6b 100755 --- a/spec/unit/type/file/ensure_spec.rb +++ b/spec/unit/type/file/ensure_spec.rb @@ -1,123 +1,123 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/type/file/ensure' describe Puppet::Type::File::Ensure do include PuppetSpec::Files let(:path) { tmpfile('file_ensure') } let(:resource) { Puppet::Type.type(:file).new(:ensure => 'file', :path => path, :replace => true) } let(:property) { resource.property(:ensure) } it "should be a subclass of Ensure" do described_class.superclass.must == Puppet::Property::Ensure end describe "when retrieving the current state" do it "should return :absent if the file does not exist" do resource.expects(:stat).returns nil property.retrieve.should == :absent end it "should return the current file type if the file exists" do stat = mock 'stat', :ftype => "directory" resource.expects(:stat).returns stat property.retrieve.should == :directory end end describe "when testing whether :ensure is in sync" do it "should always be in sync if replace is 'false' unless the file is missing" do property.should = :file resource.expects(:replace?).returns false - property.safe_insync?(:link).should be_true + property.safe_insync?(:link).should be_truthy end it "should be in sync if :ensure is set to :absent and the file does not exist" do property.should = :absent property.must be_safe_insync(:absent) end it "should not be in sync if :ensure is set to :absent and the file exists" do property.should = :absent property.should_not be_safe_insync(:file) end it "should be in sync if a normal file exists and :ensure is set to :present" do property.should = :present property.must be_safe_insync(:file) end it "should be in sync if a directory exists and :ensure is set to :present" do property.should = :present property.must be_safe_insync(:directory) end it "should be in sync if a symlink exists and :ensure is set to :present" do property.should = :present property.must be_safe_insync(:link) end it "should not be in sync if :ensure is set to :file and a directory exists" do property.should = :file property.should_not be_safe_insync(:directory) end end describe "#sync" do context "directory" do before :each do resource[:ensure] = :directory end it "should raise if the parent directory doesn't exist" do newpath = File.join(path, 'nonexistentparent', 'newdir') resource[:path] = newpath expect { property.sync }.to raise_error(Puppet::Error, /Cannot create #{newpath}; parent directory #{File.dirname(newpath)} does not exist/) end it "should accept octal mode as fixnum" do resource[:mode] = '0700' resource.expects(:property_fix) Dir.expects(:mkdir).with(path, 0700) property.sync end it "should accept octal mode as string" do resource[:mode] = "700" resource.expects(:property_fix) Dir.expects(:mkdir).with(path, 0700) property.sync end it "should accept octal mode as string with leading zero" do resource[:mode] = "0700" resource.expects(:property_fix) Dir.expects(:mkdir).with(path, 0700) property.sync end it "should accept symbolic mode" do resource[:mode] = "u=rwx,go=x" resource.expects(:property_fix) Dir.expects(:mkdir).with(path, 0711) property.sync end end end end diff --git a/spec/unit/type/file/group_spec.rb b/spec/unit/type/file/group_spec.rb index c2a38d07b..97d3d31b5 100755 --- a/spec/unit/type/file/group_spec.rb +++ b/spec/unit/type/file/group_spec.rb @@ -1,60 +1,60 @@ #! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:file).attrclass(:group) do include PuppetSpec::Files let(:path) { tmpfile('mode_spec') } let(:resource) { Puppet::Type.type(:file).new :path => path, :group => 'users' } let(:group) { resource.property(:group) } before :each do # If the provider was already loaded without root, it won't have the # feature, so we have to add it here to test. Puppet::Type.type(:file).defaultprovider.has_feature :manages_ownership end describe "#insync?" do before :each do resource[:group] = ['foos', 'bars'] resource.provider.stubs(:name2gid).with('foos').returns 1001 resource.provider.stubs(:name2gid).with('bars').returns 1002 end it "should fail if a group's id can't be found by name" do resource.provider.stubs(:name2gid).returns nil expect { group.insync?(5) }.to raise_error(/Could not find group foos/) end it "should use the id for comparisons, not the name" do - group.insync?('foos').should be_false + group.insync?('foos').should be_falsey end it "should return true if the current group is one of the desired group" do - group.insync?(1001).should be_true + group.insync?(1001).should be_truthy end it "should return false if the current group is not one of the desired group" do - group.insync?(1003).should be_false + group.insync?(1003).should be_falsey end end %w[is_to_s should_to_s].each do |prop_to_s| describe "##{prop_to_s}" do it "should use the name of the user if it can find it" do resource.provider.stubs(:gid2name).with(1001).returns 'foos' group.send(prop_to_s, 1001).should == 'foos' end it "should use the id of the user if it can't" do resource.provider.stubs(:gid2name).with(1001).returns nil group.send(prop_to_s, 1001).should == 1001 end end end end diff --git a/spec/unit/type/file/owner_spec.rb b/spec/unit/type/file/owner_spec.rb index 3a628b2c4..194bcf3f6 100755 --- a/spec/unit/type/file/owner_spec.rb +++ b/spec/unit/type/file/owner_spec.rb @@ -1,58 +1,58 @@ #! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:file).attrclass(:owner) do include PuppetSpec::Files let(:path) { tmpfile('mode_spec') } let(:resource) { Puppet::Type.type(:file).new :path => path, :owner => 'joeuser' } let(:owner) { resource.property(:owner) } before :each do Puppet.features.stubs(:root?).returns(true) end describe "#insync?" do before :each do resource[:owner] = ['foo', 'bar'] resource.provider.stubs(:name2uid).with('foo').returns 1001 resource.provider.stubs(:name2uid).with('bar').returns 1002 end it "should fail if an owner's id can't be found by name" do resource.provider.stubs(:name2uid).returns nil expect { owner.insync?(5) }.to raise_error(/Could not find user foo/) end it "should use the id for comparisons, not the name" do - owner.insync?('foo').should be_false + owner.insync?('foo').should be_falsey end it "should return true if the current owner is one of the desired owners" do - owner.insync?(1001).should be_true + owner.insync?(1001).should be_truthy end it "should return false if the current owner is not one of the desired owners" do - owner.insync?(1003).should be_false + owner.insync?(1003).should be_falsey end end %w[is_to_s should_to_s].each do |prop_to_s| describe "##{prop_to_s}" do it "should use the name of the user if it can find it" do resource.provider.stubs(:uid2name).with(1001).returns 'foo' owner.send(prop_to_s, 1001).should == 'foo' end it "should use the id of the user if it can't" do resource.provider.stubs(:uid2name).with(1001).returns nil owner.send(prop_to_s, 1001).should == 1001 end end end end diff --git a/spec/unit/type/file_spec.rb b/spec/unit/type/file_spec.rb index c99aec494..7b32129f6 100755 --- a/spec/unit/type/file_spec.rb +++ b/spec/unit/type/file_spec.rb @@ -1,1504 +1,1504 @@ #! /usr/bin/env ruby 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 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 triple 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 and collapse a double-slash at the start of the path" do file[:path] = "//tmp/xxx" file[:path].should == '/tmp/xxx' end it "should accept and collapse a triple-slash at the start of the path" do file[:path] = "///tmp/xxx" file[:path].should == '/tmp/xxx' end 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" do file[:path] = "X:/" file[:path].should == "X:/" end it "should not accept a drive letter without a slash" do expect { file[:path] = "X:" }.to raise_error(/File paths must be fully qualified/) end describe "when using UNC filenames", :if => Puppet.features.microsoft_windows? do it "should remove trailing slashes" do file[:path] = "//localhost/foo/bar/baz/" file[:path].should == "//localhost/foo/bar/baz" end it "should remove double slashes" do file[:path] = "//localhost/foo/bar//baz" file[:path].should == "//localhost/foo/bar/baz" end it "should remove trailing double slashes" do file[:path] = "//localhost/foo/bar/baz//" file[:path].should == "//localhost/foo/bar/baz" end it "should remove a trailing slash from a sharename" do file[:path] = "//localhost/foo/" file[:path].should == "//localhost/foo" end it "should not modify a sharename" do file[:path] = "//localhost/foo" file[:path].should == "//localhost/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 + file[:recurse].should be_falsey end [true, "true", "remote"].each do |value| it "should consider #{value} to enable recursion" do file[:recurse] = value - file[:recurse].should be_true + file[:recurse].should be_truthy end end it "should not allow numbers" do expect { file[:recurse] = 10 }.to raise_error( Puppet::Error, /Parameter recurse failed on File\[[^\]]+\]: Invalid recurse value 10/) end [false, "false"].each do |value| it "should consider #{value} to disable recursion" do file[:recurse] = value - file[:recurse].should be_false + file[:recurse].should be_falsey end 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 be_true + file[:replace].should be_truthy end end [false, :false, :no].each do |value| it "should consider #{value} to be false" do file[:replace] = value - file[:replace].should be_false + file[:replace].should be_falsey end end end describe ".instances" do it "should return an empty array" do described_class.instances.should == [] 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::Util::SUIDManager.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 "#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", :if => described_class.defaultprovider.feature?(:manages_symlinks) do file = described_class.new(:path => path, :target => File.expand_path(__FILE__)) 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", :if => described_class.defaultprovider.feature?(:manages_symlinks) 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", :if => described_class.defaultprovider.feature?(:manages_symlinks) 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", :uses_checksums => true 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. with_digest_algorithms do it "it should set the checksum type to #{metadata[:digest_algorithm]} 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, digest_algorithm.intern) file.recurse_remote("first" => @resource) end 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 == false end it "should fail if it can't backup the file" do file.stubs(:stat).returns stub('stat', :ftype => 'file') file.stubs(:perform_backup).returns false expect { file.remove_existing(:file) }.to raise_error(Puppet::Error, /Could not back up; will not replace/) end describe "backing up directories" do it "should not backup directories if force is false" do file[:force] = false file.stubs(:stat).returns stub('stat', :ftype => 'directory') file.expects(:perform_backup).never file.remove_existing(:file).should == false end it "should backup directories if force is true" do file[:force] = true FileUtils.expects(:rmtree).with(file[:path]) file.stubs(:stat).returns stub('stat', :ftype => 'directory') file.expects(:perform_backup).once.returns(true) file.remove_existing(:file).should == true end 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 == false 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 Puppet::FileSystem.exist?(file[:path]).should == false end it "should remove an existing link", :if => described_class.defaultprovider.feature?(:manages_symlinks) do file.stubs(:perform_backup).returns true target = tmpfile('link_target') FileUtils.touch(target) Puppet::FileSystem.symlink(target, path) file[:target] = target file.remove_existing(:directory).should == true Puppet::FileSystem.exist?(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') Puppet::FileSystem.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", :if => described_class.defaultprovider.feature?(:manages_symlinks) do before do target = tmpfile('link_target') FileUtils.touch(target) Puppet::FileSystem.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] = make_absolute('/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 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) 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) expect { file.write :NOTUSED }.to_not raise_error end end describe "when resource mode is supplied" do before { file.stubs(:property_fix) } context "and writing temporary files" do before { file.stubs(:write_temporary_file?).returns(true) } it "should convert symbolic mode to int" do file[:mode] = 'oga=r' Puppet::Util.expects(:replace_file).with(file[:path], 0444) file.write :NOTUSED end it "should support int modes" do file[:mode] = '0444' Puppet::Util.expects(:replace_file).with(file[:path], 0444) file.write :NOTUSED end end context "and not writing temporary files" do before { file.stubs(:write_temporary_file?).returns(false) } it "should set a umask of 0" do file[:mode] = 'oga=r' Puppet::Util.expects(:withumask).with(0) file.write :NOTUSED end it "should convert symbolic mode to int" do file[:mode] = 'oga=r' File.expects(:open).with(file[:path], anything, 0444) file.write :NOTUSED end it "should support int modes" do file[:mode] = '0444' File.expects(:open).with(file[:path], anything, 0444) file.write :NOTUSED end end end describe "when resource mode is not supplied" do context "and content is supplied" do it "should default to 0644 mode" do file = described_class.new(:path => path, :content => "file content") file.write :NOTUSED expect(File.stat(file[:path]).mode & 0777).to eq(0644) end end context "and no content is supplied" do it "should use puppet's default umask of 022" do file = described_class.new(:path => path) umask_from_the_user = 0777 Puppet::Util.withumask(umask_from_the_user) do file.write :NOTUSED end expect(File.stat(file[:path]).mode & 0777).to eq(0644) end 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 + file.send(:write_temporary_file?).should be_truthy 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 + file.send(:write_temporary_file?).should be_truthy end it "should be false if the file has neither content nor source" do - file.send(:write_temporary_file?).should be_false + file.send(:write_temporary_file?).should be_falsey 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", :if => described_class.defaultprovider.feature?(:manages_symlinks) do file = described_class.new(:path => File.expand_path("/foo"), :ensure => :directory) 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", :if => described_class.defaultprovider.feature?(:manages_symlinks) do 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] = '//localhost/foo/bar/baz' dir = described_class.new(:path => "//localhost/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 => "//localhost/foo/bar/baz/qux") dir = described_class.new(:path => "//localhost/foo/bar/baz") grandparent = described_class.new(:path => "//localhost/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 => "//localhost/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 => "//localhost/foo") catalog.add_resource file puts file.autorequire file.autorequire.should be_empty end end end end end describe "when managing links", :if => Puppet.features.manages_symlinks? do require 'tempfile' before :each do Dir.mkdir(path) @target = File.join(path, "target") @link = File.join(path, "link") target = described_class.new( :ensure => :file, :path => @target, :catalog => catalog, :content => 'yayness', :mode => '0644') catalog.add_resource target @link_resource = described_class.new( :ensure => :link, :path => @link, :target => @target, :catalog => catalog, :mode => '0755') catalog.add_resource @link_resource # to prevent the catalog from trying to write state.yaml Puppet::Util::Storage.stubs(:store) end it "should preserve the original file mode and ignore the one set by the link" do @link_resource[:links] = :manage # default catalog.apply # I convert them to strings so they display correctly if there's an error. (Puppet::FileSystem.stat(@target).mode & 007777).to_s(8).should == '644' end it "should manage the mode of the followed link" do pending("Windows cannot presently manage the mode when following symlinks", :if => Puppet.features.microsoft_windows?) do @link_resource[:links] = :follow catalog.apply (Puppet::FileSystem.stat(@target).mode & 007777).to_s(8).should == '755' end 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 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 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 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 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 - Puppet::FileSystem.exist?(path).should be_true + Puppet::FileSystem.exist?(path).should be_truthy @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/group_spec.rb b/spec/unit/type/group_spec.rb index ffadfac26..e1d0fdfeb 100755 --- a/spec/unit/type/group_spec.rb +++ b/spec/unit/type/group_spec.rb @@ -1,84 +1,84 @@ #! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:group) do before do @class = Puppet::Type.type(:group) end it "should have a system_groups feature" do @class.provider_feature(:system_groups).should_not be_nil end describe "when validating attributes" do [:name, :allowdupe].each do |param| it "should have a #{param} parameter" do @class.attrtype(param).should == :param end end [:ensure, :gid].each do |param| it "should have a #{param} property" do @class.attrtype(param).should == :property end end it "should convert gids provided as strings into integers" do @class.new(:name => "foo", :gid => "15")[:gid].should == 15 end it "should accepts gids provided as integers" do @class.new(:name => "foo", :gid => 15)[:gid].should == 15 end end it "should have a boolean method for determining if duplicates are allowed" do @class.new(:name => "foo").must respond_to "allowdupe?" end it "should have a boolean method for determining if system groups are allowed" do @class.new(:name => "foo").must respond_to "system?" end it "should call 'create' to create the group" do group = @class.new(:name => "foo", :ensure => :present) group.provider.expects(:create) group.parameter(:ensure).sync end it "should call 'delete' to remove the group" do group = @class.new(:name => "foo", :ensure => :absent) group.provider.expects(:delete) group.parameter(:ensure).sync end it "delegates the existence check to its provider" do provider = @class.provide(:testing) {} provider_instance = provider.new provider_instance.expects(:exists?).returns true type = @class.new(:name => "group", :provider => provider_instance) type.exists?.should == true end describe "should delegate :members implementation to the provider:" do let (:provider) { @class.provide(:testing) { has_features :manages_members } } let (:provider_instance) { provider.new } let (:type) { @class.new(:name => "group", :provider => provider_instance, :members => ['user1']) } it "insync? calls members_insync?" do provider_instance.expects(:members_insync?).with(['user1'], ['user1']).returns true - type.property(:members).insync?(['user1']).should be_true + type.property(:members).insync?(['user1']).should be_truthy end it "is_to_s and should_to_s call members_to_s" do provider_instance.expects(:members_to_s).with(['user2', 'user1']).returns "user2 (), user1 ()" provider_instance.expects(:members_to_s).with(['user1']).returns "user1 ()" type.property(:members).is_to_s('user1').should == 'user1 ()' type.property(:members).should_to_s('user2,user1').should == 'user2 (), user1 ()' end end end diff --git a/spec/unit/type/k5login_spec.rb b/spec/unit/type/k5login_spec.rb index 6c0dbb16d..f3d4d3b1e 100755 --- a/spec/unit/type/k5login_spec.rb +++ b/spec/unit/type/k5login_spec.rb @@ -1,115 +1,115 @@ #!/usr/bin/env ruby require 'spec_helper' require 'fileutils' require 'puppet/type' describe Puppet::Type.type(:k5login), :unless => Puppet.features.microsoft_windows? do include PuppetSpec::Files context "the type class" do subject { described_class } it { should be_validattr :ensure } it { should be_validattr :path } it { should be_validattr :principals } it { should be_validattr :mode } # We have one, inline provider implemented. it { should be_validattr :provider } end let(:path) { tmpfile('k5login') } def resource(attrs = {}) attrs = { :ensure => 'present', :path => path, :principals => 'fred@EXAMPLE.COM' }.merge(attrs) if content = attrs.delete(:content) File.open(path, 'w') { |f| f.print(content) } end resource = described_class.new(attrs) resource end before :each do FileUtils.touch(path) end context "the provider" do context "when the file is missing" do it "should initially be absent" do File.delete(path) resource.retrieve[:ensure].must == :absent end it "should create the file when synced" do resource(:ensure => 'present').parameter(:ensure).sync - Puppet::FileSystem.exist?(path).should be_true + Puppet::FileSystem.exist?(path).should be_truthy end end context "when the file is present" do context "retrieved initial state" do subject { resource.retrieve } it "should retrieve its properties correctly with zero principals" do subject[:ensure].should == :present subject[:principals].should == [] # We don't really care what the mode is, just that it got it subject[:mode].should_not be_nil end context "with one principal" do subject { resource(:content => "daniel@EXAMPLE.COM\n").retrieve } it "should retrieve its principals correctly" do subject[:principals].should == ["daniel@EXAMPLE.COM"] end end context "with two principals" do subject do content = ["daniel@EXAMPLE.COM", "george@EXAMPLE.COM"].join("\n") resource(:content => content).retrieve end it "should retrieve its principals correctly" do subject[:principals].should == ["daniel@EXAMPLE.COM", "george@EXAMPLE.COM"] end end end it "should remove the file ensure is absent" do resource(:ensure => 'absent').property(:ensure).sync - Puppet::FileSystem.exist?(path).should be_false + Puppet::FileSystem.exist?(path).should be_falsey end it "should write one principal to the file" do File.read(path).should == "" resource(:principals => ["daniel@EXAMPLE.COM"]).property(:principals).sync File.read(path).should == "daniel@EXAMPLE.COM\n" end it "should write multiple principals to the file" do content = ["daniel@EXAMPLE.COM", "george@EXAMPLE.COM"] File.read(path).should == "" resource(:principals => content).property(:principals).sync File.read(path).should == content.join("\n") + "\n" end describe "when setting the mode" do # The defined input type is "mode, as an octal string" ["400", "600", "700", "644", "664"].each do |mode| it "should update the mode to #{mode}" do resource(:mode => mode).property(:mode).sync (Puppet::FileSystem.stat(path).mode & 07777).to_s(8).should == mode end end end end end end diff --git a/spec/unit/type/noop_metaparam_spec.rb b/spec/unit/type/noop_metaparam_spec.rb index 895924b0e..f790eb91a 100755 --- a/spec/unit/type/noop_metaparam_spec.rb +++ b/spec/unit/type/noop_metaparam_spec.rb @@ -1,38 +1,38 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/type' describe Puppet::Type.type(:file).attrclass(:noop) do include PuppetSpec::Files before do Puppet.settings.stubs(:use) @file = Puppet::Type.newfile :path => make_absolute("/what/ever") end it "should accept true as a value" do lambda { @file[:noop] = true }.should_not raise_error end it "should accept false as a value" do lambda { @file[:noop] = false }.should_not raise_error end describe "when set on a resource" do it "should default to the :noop setting" do Puppet[:noop] = true @file.noop.should == true end it "should prefer true values from the attribute" do @file[:noop] = true - @file.noop.should be_true + @file.noop.should be_truthy end it "should prefer false values from the attribute" do @file[:noop] = false - @file.noop.should be_false + @file.noop.should be_falsey end end end diff --git a/spec/unit/type/package_spec.rb b/spec/unit/type/package_spec.rb index 7f5e15847..58bfc0000 100755 --- a/spec/unit/type/package_spec.rb +++ b/spec/unit/type/package_spec.rb @@ -1,374 +1,374 @@ #! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:package) do before do Puppet::Util::Storage.stubs(:store) end it "should have a :reinstallable feature that requires the :reinstall method" do Puppet::Type.type(:package).provider_feature(:reinstallable).methods.should == [:reinstall] end it "should have an :installable feature that requires the :install method" do Puppet::Type.type(:package).provider_feature(:installable).methods.should == [:install] end it "should have an :uninstallable feature that requires the :uninstall method" do Puppet::Type.type(:package).provider_feature(:uninstallable).methods.should == [:uninstall] end it "should have an :upgradeable feature that requires :update and :latest methods" do Puppet::Type.type(:package).provider_feature(:upgradeable).methods.should == [:update, :latest] end it "should have a :purgeable feature that requires the :purge latest method" do Puppet::Type.type(:package).provider_feature(:purgeable).methods.should == [:purge] end it "should have a :versionable feature" do Puppet::Type.type(:package).provider_feature(:versionable).should_not be_nil end it "should have a :package_settings feature that requires :package_settings_insync?, :package_settings and :package_settings=" do Puppet::Type.type(:package).provider_feature(:package_settings).methods.should == [:package_settings_insync?, :package_settings, :package_settings=] end it "should default to being installed" do pkg = Puppet::Type.type(:package).new(:name => "yay", :provider => :apt) pkg.should(:ensure).should == :present end describe "when validating attributes" do [:name, :source, :instance, :status, :adminfile, :responsefile, :configfiles, :category, :platform, :root, :vendor, :description, :allowcdrom, :allow_virtual, :reinstall_on_refresh].each do |param| it "should have a #{param} parameter" do Puppet::Type.type(:package).attrtype(param).should == :param end end it "should have an ensure property" do Puppet::Type.type(:package).attrtype(:ensure).should == :property end it "should have a package_settings property" do Puppet::Type.type(:package).attrtype(:package_settings).should == :property end end describe "when validating attribute values" do before :each do @provider = stub( 'provider', :class => Puppet::Type.type(:package).defaultprovider, :clear => nil, :validate_source => nil ) Puppet::Type.type(:package).defaultprovider.stubs(:new).returns(@provider) end after :each do Puppet::Type.type(:package).defaultprovider = nil end it "should support :present as a value to :ensure" do Puppet::Type.type(:package).new(:name => "yay", :ensure => :present) end it "should alias :installed to :present as a value to :ensure" do pkg = Puppet::Type.type(:package).new(:name => "yay", :ensure => :installed) pkg.should(:ensure).should == :present end it "should support :absent as a value to :ensure" do Puppet::Type.type(:package).new(:name => "yay", :ensure => :absent) end it "should support :purged as a value to :ensure if the provider has the :purgeable feature" do @provider.expects(:satisfies?).with([:purgeable]).returns(true) Puppet::Type.type(:package).new(:name => "yay", :ensure => :purged) end it "should not support :purged as a value to :ensure if the provider does not have the :purgeable feature" do @provider.expects(:satisfies?).with([:purgeable]).returns(false) expect { Puppet::Type.type(:package).new(:name => "yay", :ensure => :purged) }.to raise_error(Puppet::Error) end it "should support :latest as a value to :ensure if the provider has the :upgradeable feature" do @provider.expects(:satisfies?).with([:upgradeable]).returns(true) Puppet::Type.type(:package).new(:name => "yay", :ensure => :latest) end it "should not support :latest as a value to :ensure if the provider does not have the :upgradeable feature" do @provider.expects(:satisfies?).with([:upgradeable]).returns(false) expect { Puppet::Type.type(:package).new(:name => "yay", :ensure => :latest) }.to raise_error(Puppet::Error) end it "should support version numbers as a value to :ensure if the provider has the :versionable feature" do @provider.expects(:satisfies?).with([:versionable]).returns(true) Puppet::Type.type(:package).new(:name => "yay", :ensure => "1.0") end it "should not support version numbers as a value to :ensure if the provider does not have the :versionable feature" do @provider.expects(:satisfies?).with([:versionable]).returns(false) expect { Puppet::Type.type(:package).new(:name => "yay", :ensure => "1.0") }.to raise_error(Puppet::Error) end it "should accept any string as an argument to :source" do expect { Puppet::Type.type(:package).new(:name => "yay", :source => "stuff") }.to_not raise_error end it "should not accept a non-string name" do expect do Puppet::Type.type(:package).new(:name => ["error"]) end.to raise_error(Puppet::ResourceError, /Name must be a String/) end end module PackageEvaluationTesting def setprops(properties) @provider.stubs(:properties).returns(properties) end end describe Puppet::Type.type(:package) do before :each do @provider = stub( 'provider', :class => Puppet::Type.type(:package).defaultprovider, :clear => nil, :satisfies? => true, :name => :mock, :validate_source => nil ) Puppet::Type.type(:package).defaultprovider.stubs(:new).returns(@provider) Puppet::Type.type(:package).defaultprovider.stubs(:instances).returns([]) @package = Puppet::Type.type(:package).new(:name => "yay") @catalog = Puppet::Resource::Catalog.new @catalog.add_resource(@package) end describe Puppet::Type.type(:package), "when it should be purged" do include PackageEvaluationTesting before { @package[:ensure] = :purged } it "should do nothing if it is :purged" do @provider.expects(:properties).returns(:ensure => :purged).at_least_once @catalog.apply end [:absent, :installed, :present, :latest].each do |state| it "should purge if it is #{state.to_s}" do @provider.stubs(:properties).returns(:ensure => state) @provider.expects(:purge) @catalog.apply end end end describe Puppet::Type.type(:package), "when it should be absent" do include PackageEvaluationTesting before { @package[:ensure] = :absent } [:purged, :absent].each do |state| it "should do nothing if it is #{state.to_s}" do @provider.expects(:properties).returns(:ensure => state).at_least_once @catalog.apply end end [:installed, :present, :latest].each do |state| it "should uninstall if it is #{state.to_s}" do @provider.stubs(:properties).returns(:ensure => state) @provider.expects(:uninstall) @catalog.apply end end end describe Puppet::Type.type(:package), "when it should be present" do include PackageEvaluationTesting before { @package[:ensure] = :present } [:present, :latest, "1.0"].each do |state| it "should do nothing if it is #{state.to_s}" do @provider.expects(:properties).returns(:ensure => state).at_least_once @catalog.apply end end [:purged, :absent].each do |state| it "should install if it is #{state.to_s}" do @provider.stubs(:properties).returns(:ensure => state) @provider.expects(:install) @catalog.apply end end end describe Puppet::Type.type(:package), "when it should be latest" do include PackageEvaluationTesting before { @package[:ensure] = :latest } [:purged, :absent].each do |state| it "should upgrade if it is #{state.to_s}" do @provider.stubs(:properties).returns(:ensure => state) @provider.expects(:update) @catalog.apply end end it "should upgrade if the current version is not equal to the latest version" do @provider.stubs(:properties).returns(:ensure => "1.0") @provider.stubs(:latest).returns("2.0") @provider.expects(:update) @catalog.apply end it "should do nothing if it is equal to the latest version" do @provider.stubs(:properties).returns(:ensure => "1.0") @provider.stubs(:latest).returns("1.0") @provider.expects(:update).never @catalog.apply end it "should do nothing if the provider returns :present as the latest version" do @provider.stubs(:properties).returns(:ensure => :present) @provider.stubs(:latest).returns("1.0") @provider.expects(:update).never @catalog.apply end end describe Puppet::Type.type(:package), "when it should be a specific version" do include PackageEvaluationTesting before { @package[:ensure] = "1.0" } [:purged, :absent].each do |state| it "should install if it is #{state.to_s}" do @provider.stubs(:properties).returns(:ensure => state) - @package.property(:ensure).insync?(state).should be_false + @package.property(:ensure).insync?(state).should be_falsey @provider.expects(:install) @catalog.apply end end it "should do nothing if the current version is equal to the desired version" do @provider.stubs(:properties).returns(:ensure => "1.0") - @package.property(:ensure).insync?('1.0').should be_true + @package.property(:ensure).insync?('1.0').should be_truthy @provider.expects(:install).never @catalog.apply end it "should install if the current version is not equal to the specified version" do @provider.stubs(:properties).returns(:ensure => "2.0") - @package.property(:ensure).insync?('2.0').should be_false + @package.property(:ensure).insync?('2.0').should be_falsey @provider.expects(:install) @catalog.apply end describe "when current value is an array" do let(:installed_versions) { ["1.0", "2.0", "3.0"] } before (:each) do @provider.stubs(:properties).returns(:ensure => installed_versions) end it "should install if value not in the array" do @package[:ensure] = "1.5" - @package.property(:ensure).insync?(installed_versions).should be_false + @package.property(:ensure).insync?(installed_versions).should be_falsey @provider.expects(:install) @catalog.apply end it "should not install if value is in the array" do @package[:ensure] = "2.0" - @package.property(:ensure).insync?(installed_versions).should be_true + @package.property(:ensure).insync?(installed_versions).should be_truthy @provider.expects(:install).never @catalog.apply end describe "when ensure is set to 'latest'" do it "should not install if the value is in the array" do @provider.expects(:latest).returns("3.0") @package[:ensure] = "latest" - @package.property(:ensure).insync?(installed_versions).should be_true + @package.property(:ensure).insync?(installed_versions).should be_truthy @provider.expects(:install).never @catalog.apply end end end end describe Puppet::Type.type(:package), "when responding to refresh" do include PackageEvaluationTesting it "should support :true as a value to :reinstall_on_refresh" do srv = Puppet::Type.type(:package).new(:name => "yay", :reinstall_on_refresh => :true) srv[:reinstall_on_refresh].should == :true end it "should support :false as a value to :reinstall_on_refresh" do srv = Puppet::Type.type(:package).new(:name => "yay", :reinstall_on_refresh => :false) srv[:reinstall_on_refresh].should == :false end it "should specify :false as the default value of :reinstall_on_refresh" do srv = Puppet::Type.type(:package).new(:name => "yay") srv[:reinstall_on_refresh].should == :false end [:latest, :present, :installed].each do |state| it "should reinstall if it should be #{state.to_s} and reinstall_on_refresh is true" do @package[:ensure] = state @package[:reinstall_on_refresh] = :true @provider.stubs(:reinstallable?).returns(true) @provider.expects(:reinstall).once @package.refresh end it "should reinstall if it should be #{state.to_s} and reinstall_on_refresh is false" do @package[:ensure] = state @package[:reinstall_on_refresh] = :false @provider.stubs(:reinstallable?).returns(true) @provider.expects(:reinstall).never @package.refresh end end [:purged, :absent, :held].each do |state| it "should not reinstall if it should be #{state.to_s} and reinstall_on_refresh is true" do @package[:ensure] = state @provider.stubs(:reinstallable?).returns(true) @provider.expects(:reinstall).never @package.refresh end it "should not reinstall if it should be #{state.to_s} and reinstall_on_refresh is false" do @package[:ensure] = state @provider.stubs(:reinstallable?).returns(true) @provider.expects(:reinstall).never @package.refresh end end end end describe "allow_virtual" do it "defaults to true on platforms that support virtual packages" do pkg = Puppet::Type.type(:package).new(:name => 'yay', :provider => :yum) expect(pkg[:allow_virtual]).to eq true end it "defaults to false on platforms that do not support virtual packages" do pkg = Puppet::Type.type(:package).new(:name => 'yay', :provider => :apple) expect(pkg[:allow_virtual]).to be_nil end end end diff --git a/spec/unit/type/resources_spec.rb b/spec/unit/type/resources_spec.rb index e985b9752..ab2165672 100755 --- a/spec/unit/type/resources_spec.rb +++ b/spec/unit/type/resources_spec.rb @@ -1,318 +1,318 @@ #! /usr/bin/env ruby require 'spec_helper' resources = Puppet::Type.type(:resources) # There are still plenty of tests to port over from test/. describe resources do before :each do described_class.reset_system_users_max_uid! end describe "when initializing" do it "should fail if the specified resource type does not exist" do Puppet::Type.stubs(:type).with { |x| x.to_s.downcase == "resources"}.returns resources Puppet::Type.expects(:type).with("nosuchtype").returns nil lambda { resources.new :name => "nosuchtype" }.should raise_error(Puppet::Error) end it "should not fail when the specified resource type exists" do lambda { resources.new :name => "file" }.should_not raise_error end it "should set its :resource_type attribute" do resources.new(:name => "file").resource_type.should == Puppet::Type.type(:file) end end describe :purge do let (:instance) { described_class.new(:name => 'file') } it "defaults to false" do - instance[:purge].should be_false + instance[:purge].should be_falsey end it "can be set to false" do instance[:purge] = 'false' end it "cannot be set to true for a resource type that does not accept ensure" do instance.resource_type.stubs(:respond_to?).returns true instance.resource_type.stubs(:validproperty?).returns false expect { instance[:purge] = 'yes' }.to raise_error Puppet::Error end it "cannot be set to true for a resource type that does not have instances" do instance.resource_type.stubs(:respond_to?).returns false instance.resource_type.stubs(:validproperty?).returns true expect { instance[:purge] = 'yes' }.to raise_error Puppet::Error end it "can be set to true for a resource type that has instances and can accept ensure" do instance.resource_type.stubs(:respond_to?).returns true instance.resource_type.stubs(:validproperty?).returns true expect { instance[:purge] = 'yes' }.to_not raise_error end end describe "#check_user purge behaviour" do describe "with unless_system_user => true" do before do @res = Puppet::Type.type(:resources).new :name => :user, :purge => true, :unless_system_user => true @res.catalog = Puppet::Resource::Catalog.new Puppet::FileSystem.stubs(:exist?).with('/etc/login.defs').returns false end it "should never purge hardcoded system users" do %w{root nobody bin noaccess daemon sys}.each do |sys_user| - @res.user_check(Puppet::Type.type(:user).new(:name => sys_user)).should be_false + @res.user_check(Puppet::Type.type(:user).new(:name => sys_user)).should be_falsey end end it "should not purge system users if unless_system_user => true" do user_hash = {:name => 'system_user', :uid => 125, :system => true} user = Puppet::Type.type(:user).new(user_hash) user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash) - @res.user_check(user).should be_false + @res.user_check(user).should be_falsey end it "should purge non-system users if unless_system_user => true" do user_hash = {:name => 'system_user', :uid => described_class.system_users_max_uid + 1, :system => true} user = Puppet::Type.type(:user).new(user_hash) user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash) - @res.user_check(user).should be_true + @res.user_check(user).should be_truthy end it "should not purge system users under 600 if unless_system_user => 600" do res = Puppet::Type.type(:resources).new :name => :user, :purge => true, :unless_system_user => 600 res.catalog = Puppet::Resource::Catalog.new user_hash = {:name => 'system_user', :uid => 500, :system => true} user = Puppet::Type.type(:user).new(user_hash) user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash) - res.user_check(user).should be_false + res.user_check(user).should be_falsey end end %w(FreeBSD OpenBSD).each do |os| describe "on #{os}" do before :each do Facter.stubs(:value).with(:kernel).returns(os) Facter.stubs(:value).with(:operatingsystem).returns(os) Facter.stubs(:value).with(:osfamily).returns(os) Puppet::FileSystem.stubs(:exist?).with('/etc/login.defs').returns false @res = Puppet::Type.type(:resources).new :name => :user, :purge => true, :unless_system_user => true @res.catalog = Puppet::Resource::Catalog.new end it "should not purge system users under 1000" do user_hash = {:name => 'system_user', :uid => 999} user = Puppet::Type.type(:user).new(user_hash) user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash) - @res.user_check(user).should be_false + @res.user_check(user).should be_falsey end it "should purge users over 999" do user_hash = {:name => 'system_user', :uid => 1000} user = Puppet::Type.type(:user).new(user_hash) user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash) - @res.user_check(user).should be_true + @res.user_check(user).should be_truthy end end end describe 'with login.defs present' do before :each do Puppet::FileSystem.expects(:exist?).with('/etc/login.defs').returns true Puppet::FileSystem.expects(:each_line).with('/etc/login.defs').yields(' UID_MIN 1234 # UID_MIN comment ') @res = Puppet::Type.type(:resources).new :name => :user, :purge => true, :unless_system_user => true @res.catalog = Puppet::Resource::Catalog.new end it 'should not purge a system user' do user_hash = {:name => 'system_user', :uid => 1233} user = Puppet::Type.type(:user).new(user_hash) user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash) - @res.user_check(user).should be_false + @res.user_check(user).should be_falsey end it 'should purge a non-system user' do user_hash = {:name => 'system_user', :uid => 1234} user = Puppet::Type.type(:user).new(user_hash) user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash) - @res.user_check(user).should be_true + @res.user_check(user).should be_truthy end end describe "with unless_uid" do describe "with a uid array" do before do @res = Puppet::Type.type(:resources).new :name => :user, :purge => true, :unless_uid => [15_000, 15_001, 15_002] @res.catalog = Puppet::Resource::Catalog.new end it "should purge uids that are not in a specified array" do user_hash = {:name => 'special_user', :uid => 25_000} user = Puppet::Type.type(:user).new(user_hash) user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash) - @res.user_check(user).should be_true + @res.user_check(user).should be_truthy end it "should not purge uids that are in a specified array" do user_hash = {:name => 'special_user', :uid => 15000} user = Puppet::Type.type(:user).new(user_hash) user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash) - @res.user_check(user).should be_false + @res.user_check(user).should be_falsey end end describe "with a single integer uid" do before do @res = Puppet::Type.type(:resources).new :name => :user, :purge => true, :unless_uid => 15_000 @res.catalog = Puppet::Resource::Catalog.new end it "should purge uids that are not specified" do user_hash = {:name => 'special_user', :uid => 25_000} user = Puppet::Type.type(:user).new(user_hash) user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash) - @res.user_check(user).should be_true + @res.user_check(user).should be_truthy end it "should not purge uids that are specified" do user_hash = {:name => 'special_user', :uid => 15_000} user = Puppet::Type.type(:user).new(user_hash) user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash) - @res.user_check(user).should be_false + @res.user_check(user).should be_falsey end end describe "with a single string uid" do before do @res = Puppet::Type.type(:resources).new :name => :user, :purge => true, :unless_uid => '15000' @res.catalog = Puppet::Resource::Catalog.new end it "should purge uids that are not specified" do user_hash = {:name => 'special_user', :uid => 25_000} user = Puppet::Type.type(:user).new(user_hash) user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash) - @res.user_check(user).should be_true + @res.user_check(user).should be_truthy end it "should not purge uids that are specified" do user_hash = {:name => 'special_user', :uid => 15_000} user = Puppet::Type.type(:user).new(user_hash) user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash) - @res.user_check(user).should be_false + @res.user_check(user).should be_falsey end end describe "with a mixed uid array" do before do @res = Puppet::Type.type(:resources).new :name => :user, :purge => true, :unless_uid => ['15000', 16_666] @res.catalog = Puppet::Resource::Catalog.new end it "should not purge ids in the range" do user_hash = {:name => 'special_user', :uid => 15_000} user = Puppet::Type.type(:user).new(user_hash) user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash) - @res.user_check(user).should be_false + @res.user_check(user).should be_falsey end it "should not purge specified ids" do user_hash = {:name => 'special_user', :uid => 16_666} user = Puppet::Type.type(:user).new(user_hash) user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash) - @res.user_check(user).should be_false + @res.user_check(user).should be_falsey end it "should purge unspecified ids" do user_hash = {:name => 'special_user', :uid => 17_000} user = Puppet::Type.type(:user).new(user_hash) user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash) - @res.user_check(user).should be_true + @res.user_check(user).should be_truthy end end end end describe "#generate" do before do @host1 = Puppet::Type.type(:host).new(:name => 'localhost', :ip => '127.0.0.1') @catalog = Puppet::Resource::Catalog.new end describe "when dealing with non-purging resources" do before do @resources = Puppet::Type.type(:resources).new(:name => 'host') end it "should not generate any resource" do @resources.generate.should be_empty end end describe "when the catalog contains a purging resource" do before do @resources = Puppet::Type.type(:resources).new(:name => 'host', :purge => true) @purgeable_resource = Puppet::Type.type(:host).new(:name => 'localhost', :ip => '127.0.0.1') @catalog.add_resource @resources end it "should not generate a duplicate of that resource" do Puppet::Type.type(:host).stubs(:instances).returns [@host1] @catalog.add_resource @host1 @resources.generate.collect { |r| r.ref }.should_not include(@host1.ref) end it "should not include the skipped system users" do res = Puppet::Type.type(:resources).new :name => :user, :purge => true res.catalog = Puppet::Resource::Catalog.new root = Puppet::Type.type(:user).new(:name => "root") Puppet::Type.type(:user).expects(:instances).returns [ root ] list = res.generate names = list.collect { |r| r[:name] } names.should_not be_include("root") end describe "when generating a purgeable resource" do it "should be included in the generated resources" do Puppet::Type.type(:host).stubs(:instances).returns [@purgeable_resource] @resources.generate.collect { |r| r.ref }.should include(@purgeable_resource.ref) end end describe "when the instance's do not have an ensure property" do it "should not be included in the generated resources" do @no_ensure_resource = Puppet::Type.type(:exec).new(:name => "#{File.expand_path('/usr/bin/env')} echo") Puppet::Type.type(:host).stubs(:instances).returns [@no_ensure_resource] @resources.generate.collect { |r| r.ref }.should_not include(@no_ensure_resource.ref) end end describe "when the instance's ensure property does not accept absent" do it "should not be included in the generated resources" do @no_absent_resource = Puppet::Type.type(:service).new(:name => 'foobar') Puppet::Type.type(:host).stubs(:instances).returns [@no_absent_resource] @resources.generate.collect { |r| r.ref }.should_not include(@no_absent_resource.ref) end end describe "when checking the instance fails" do it "should not be included in the generated resources" do @purgeable_resource = Puppet::Type.type(:host).new(:name => 'foobar') Puppet::Type.type(:host).stubs(:instances).returns [@purgeable_resource] @resources.expects(:check).with(@purgeable_resource).returns(false) @resources.generate.collect { |r| r.ref }.should_not include(@purgeable_resource.ref) end end end end end diff --git a/spec/unit/type/schedule_spec.rb b/spec/unit/type/schedule_spec.rb index 0610bd175..caf81a713 100755 --- a/spec/unit/type/schedule_spec.rb +++ b/spec/unit/type/schedule_spec.rb @@ -1,607 +1,607 @@ #! /usr/bin/env ruby require 'spec_helper' module ScheduleTesting def diff(unit, incr, method, count) diff = Time.now.to_i.send(method, incr * count) Time.at(diff) end def day(method, count) diff(:hour, 3600 * 24, method, count) end def hour(method, count) diff(:hour, 3600, method, count) end def min(method, count) diff(:min, 60, method, count) end end describe Puppet::Type.type(:schedule) do include ScheduleTesting before :each do Puppet[:ignoreschedules] = false @schedule = Puppet::Type.type(:schedule).new(:name => "testing") end describe Puppet::Type.type(:schedule) do it "should apply to device" do @schedule.must be_appliable_to_device end it "should apply to host" do @schedule.must be_appliable_to_host end it "should default to :distance for period-matching" do @schedule[:periodmatch].must == :distance end it "should default to a :repeat of 1" do @schedule[:repeat].must == 1 end it "should never match when the period is :never" do @schedule[:period] = :never @schedule.must_not be_match end end describe Puppet::Type.type(:schedule), "when producing default schedules" do %w{hourly daily weekly monthly never}.each do |period| period = period.to_sym it "should produce a #{period} schedule with the period set appropriately" do schedules = Puppet::Type.type(:schedule).mkdefaultschedules schedules.find { |s| s[:name] == period.to_s and s[:period] == period }.must be_instance_of(Puppet::Type.type(:schedule)) end end it "should not produce default schedules when default_schedules is false" do Puppet[:default_schedules] = false schedules = Puppet::Type.type(:schedule).mkdefaultschedules schedules.must have_exactly(0).items end it "should produce a schedule named puppet with a period of hourly and a repeat of 2" do schedules = Puppet::Type.type(:schedule).mkdefaultschedules schedules.find { |s| s[:name] == "puppet" and s[:period] == :hourly and s[:repeat] == 2 }.must be_instance_of(Puppet::Type.type(:schedule)) end end describe Puppet::Type.type(:schedule), "when matching ranges" do before do Time.stubs(:now).returns(Time.local(2011, "may", 23, 11, 0, 0)) end it "should match when the start time is before the current time and the end time is after the current time" do @schedule[:range] = "10:59:50 - 11:00:10" @schedule.must be_match end it "should not match when the start time is after the current time" do @schedule[:range] = "11:00:05 - 11:00:10" @schedule.must_not be_match end it "should not match when the end time is previous to the current time" do @schedule[:range] = "10:59:50 - 10:59:55" @schedule.must_not be_match end it "should not match the current time fails between an array of ranges" do @schedule[:range] = ["4-6", "20-23"] @schedule.must_not be_match end it "should match the lower array of ranges" do @schedule[:range] = ["9-11", "14-16"] @schedule.must be_match end it "should match the upper array of ranges" do @schedule[:range] = ["4-6", "11-12"] @schedule.must be_match end end describe Puppet::Type.type(:schedule), "when matching ranges with abbreviated time specifications" do before do Time.stubs(:now).returns(Time.local(2011, "may", 23, 11, 45, 59)) end it "should match when just an hour is specified" do @schedule[:range] = "11-12" @schedule.must be_match end it "should not match when the ending hour is the current hour" do @schedule[:range] = "10-11" @schedule.must_not be_match end it "should not match when the ending minute is the current minute" do @schedule[:range] = "10:00 - 11:45" @schedule.must_not be_match end end describe Puppet::Type.type(:schedule), "when matching ranges with abbreviated time specifications, edge cases part 1" do before do Time.stubs(:now).returns(Time.local(2011, "may", 23, 11, 00, 00)) end it "should match when the current time is the start of the range using hours" do @schedule[:range] = "11 - 12" @schedule.must be_match end it "should match when the current time is the end of the range using hours" do @schedule[:range] = "10 - 11" @schedule.must be_match end it "should match when the current time is the start of the range using hours and minutes" do @schedule[:range] = "11:00 - 12:00" @schedule.must be_match end it "should match when the current time is the end of the range using hours and minutes" do @schedule[:range] = "10:00 - 11:00" @schedule.must be_match end end describe Puppet::Type.type(:schedule), "when matching ranges with abbreviated time specifications, edge cases part 2" do before do Time.stubs(:now).returns(Time.local(2011, "may", 23, 11, 00, 01)) end it "should match when the current time is just past the start of the range using hours" do @schedule[:range] = "11 - 12" @schedule.must be_match end it "should not match when the current time is just past the end of the range using hours" do @schedule[:range] = "10 - 11" @schedule.must_not be_match end it "should match when the current time is just past the start of the range using hours and minutes" do @schedule[:range] = "11:00 - 12:00" @schedule.must be_match end it "should not match when the current time is just past the end of the range using hours and minutes" do @schedule[:range] = "10:00 - 11:00" @schedule.must_not be_match end end describe Puppet::Type.type(:schedule), "when matching ranges with abbreviated time specifications, edge cases part 3" do before do Time.stubs(:now).returns(Time.local(2011, "may", 23, 10, 59, 59)) end it "should not match when the current time is just before the start of the range using hours" do @schedule[:range] = "11 - 12" @schedule.must_not be_match end it "should match when the current time is just before the end of the range using hours" do @schedule[:range] = "10 - 11" @schedule.must be_match end it "should not match when the current time is just before the start of the range using hours and minutes" do @schedule[:range] = "11:00 - 12:00" @schedule.must_not be_match end it "should match when the current time is just before the end of the range using hours and minutes" do @schedule[:range] = "10:00 - 11:00" @schedule.must be_match end end describe Puppet::Type.type(:schedule), "when matching ranges spanning days, day 1" do before do # Test with the current time at a month's end boundary to ensure we are # advancing the day properly when we push the ending limit out a day. # For example, adding 1 to 31 would throw an error instead of advancing # the date. Time.stubs(:now).returns(Time.local(2011, "mar", 31, 22, 30, 0)) end it "should match when the start time is before current time and the end time is the following day" do @schedule[:range] = "22:00:00 - 02:00:00" @schedule.must be_match end it "should not match when the current time is outside the range" do @schedule[:range] = "23:30:00 - 21:00:00" @schedule.must_not be_match end end describe Puppet::Type.type(:schedule), "when matching ranges spanning days, day 2" do before do # Test with the current time at a month's end boundary to ensure we are # advancing the day properly when we push the ending limit out a day. # For example, adding 1 to 31 would throw an error instead of advancing # the date. Time.stubs(:now).returns(Time.local(2011, "mar", 31, 1, 30, 0)) end it "should match when the start time is the day before the current time and the end time is after the current time" do @schedule[:range] = "22:00:00 - 02:00:00" @schedule.must be_match end it "should not match when the start time is after the current time" do @schedule[:range] = "02:00:00 - 00:30:00" @schedule.must_not be_match end it "should not match when the end time is before the current time" do @schedule[:range] = "22:00:00 - 01:00:00" @schedule.must_not be_match end end describe Puppet::Type.type(:schedule), "when matching hourly by distance" do before do @schedule[:period] = :hourly @schedule[:periodmatch] = :distance Time.stubs(:now).returns(Time.local(2011, "may", 23, 11, 0, 0)) end it "should match when the previous time was an hour ago" do @schedule.must be_match(hour("-", 1)) end it "should not match when the previous time was now" do @schedule.must_not be_match(Time.now) end it "should not match when the previous time was 59 minutes ago" do @schedule.must_not be_match(min("-", 59)) end end describe Puppet::Type.type(:schedule), "when matching daily by distance" do before do @schedule[:period] = :daily @schedule[:periodmatch] = :distance Time.stubs(:now).returns(Time.local(2011, "may", 23, 11, 0, 0)) end it "should match when the previous time was one day ago" do @schedule.must be_match(day("-", 1)) end it "should not match when the previous time is now" do @schedule.must_not be_match(Time.now) end it "should not match when the previous time was 23 hours ago" do @schedule.must_not be_match(hour("-", 23)) end end describe Puppet::Type.type(:schedule), "when matching weekly by distance" do before do @schedule[:period] = :weekly @schedule[:periodmatch] = :distance Time.stubs(:now).returns(Time.local(2011, "may", 23, 11, 0, 0)) end it "should match when the previous time was seven days ago" do @schedule.must be_match(day("-", 7)) end it "should not match when the previous time was now" do @schedule.must_not be_match(Time.now) end it "should not match when the previous time was six days ago" do @schedule.must_not be_match(day("-", 6)) end end describe Puppet::Type.type(:schedule), "when matching monthly by distance" do before do @schedule[:period] = :monthly @schedule[:periodmatch] = :distance Time.stubs(:now).returns(Time.local(2011, "may", 23, 11, 0, 0)) end it "should match when the previous time was 32 days ago" do @schedule.must be_match(day("-", 32)) end it "should not match when the previous time was now" do @schedule.must_not be_match(Time.now) end it "should not match when the previous time was 27 days ago" do @schedule.must_not be_match(day("-", 27)) end end describe Puppet::Type.type(:schedule), "when matching hourly by number" do before do @schedule[:period] = :hourly @schedule[:periodmatch] = :number end it "should match if the times are one minute apart and the current minute is 0" do current = Time.utc(2008, 1, 1, 0, 0, 0) previous = Time.utc(2007, 12, 31, 23, 59, 0) Time.stubs(:now).returns(current) @schedule.must be_match(previous) end it "should not match if the times are 59 minutes apart and the current minute is 59" do current = Time.utc(2009, 2, 1, 12, 59, 0) previous = Time.utc(2009, 2, 1, 12, 0, 0) Time.stubs(:now).returns(current) @schedule.must_not be_match(previous) end end describe Puppet::Type.type(:schedule), "when matching daily by number" do before do @schedule[:period] = :daily @schedule[:periodmatch] = :number end it "should match if the times are one minute apart and the current minute and hour are 0" do current = Time.utc(2010, "nov", 7, 0, 0, 0) # Now set the previous time to one minute before that previous = current - 60 Time.stubs(:now).returns(current) @schedule.must be_match(previous) end it "should not match if the times are 23 hours and 58 minutes apart and the current hour is 23 and the current minute is 59" do # Reset the previous time to 00:00:00 previous = Time.utc(2010, "nov", 7, 0, 0, 0) # Set the current time to 23:59 now = previous + (23 * 3600) + (59 * 60) Time.stubs(:now).returns(now) @schedule.must_not be_match(previous) end end describe Puppet::Type.type(:schedule), "when matching weekly by number" do before do @schedule[:period] = :weekly @schedule[:periodmatch] = :number end it "should match if the previous time is prior to the most recent Sunday" do now = Time.utc(2010, "nov", 11, 0, 0, 0) # Thursday Time.stubs(:now).returns(now) previous = Time.utc(2010, "nov", 6, 23, 59, 59) # Sat @schedule.must be_match(previous) end it "should not match if the previous time is after the most recent Saturday" do now = Time.utc(2010, "nov", 11, 0, 0, 0) # Thursday Time.stubs(:now).returns(now) previous = Time.utc(2010, "nov", 7, 0, 0, 0) # Sunday @schedule.must_not be_match(previous) end end describe Puppet::Type.type(:schedule), "when matching monthly by number" do before do @schedule[:period] = :monthly @schedule[:periodmatch] = :number end it "should match when the previous time is prior to the first day of this month" do now = Time.utc(2010, "nov", 8, 00, 59, 59) Time.stubs(:now).returns(now) previous = Time.utc(2010, "oct", 31, 23, 59, 59) @schedule.must be_match(previous) end it "should not match when the previous time is after the last day of last month" do now = Time.utc(2010, "nov", 8, 00, 59, 59) Time.stubs(:now).returns(now) previous = Time.utc(2010, "nov", 1, 0, 0, 0) @schedule.must_not be_match(previous) end end describe Puppet::Type.type(:schedule), "when matching with a repeat greater than one" do before do @schedule[:period] = :daily @schedule[:repeat] = 2 Time.stubs(:now).returns(Time.local(2011, "may", 23, 11, 0, 0)) end it "should fail if the periodmatch is 'number'" do @schedule[:periodmatch] = :number proc { @schedule[:repeat] = 2 }.must raise_error(Puppet::Error) end it "should match if the previous run was further away than the distance divided by the repeat" do previous = Time.now - (3600 * 13) @schedule.must be_match(previous) end it "should not match if the previous run was closer than the distance divided by the repeat" do previous = Time.now - (3600 * 11) @schedule.must_not be_match(previous) end end describe Puppet::Type.type(:schedule), "when matching days of the week" do before do # 2011-05-23 is a Monday Time.stubs(:now).returns(Time.local(2011, "may", 23, 11, 0, 0)) end it "should raise an error if the weekday is 'Someday'" do proc { @schedule[:weekday] = "Someday" }.should raise_error(Puppet::Error) end it "should raise an error if the weekday is '7'" do proc { @schedule[:weekday] = "7" }.should raise_error(Puppet::Error) end it "should accept all full weekday names as valid values" do proc { @schedule[:weekday] = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'] }.should_not raise_error end it "should accept all short weekday names as valid values" do proc { @schedule[:weekday] = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'] }.should_not raise_error end it "should match if the weekday is 'Monday'" do @schedule[:weekday] = "Monday" - @schedule.match?.should be_true + @schedule.match?.should be_truthy end it "should match if the weekday is 'Mon'" do @schedule[:weekday] = "Mon" - @schedule.match?.should be_true + @schedule.match?.should be_truthy end it "should match if the weekday is '1'" do @schedule[:weekday] = "1" - @schedule.match?.should be_true + @schedule.match?.should be_truthy end it "should not match if the weekday is Tuesday" do @schedule[:weekday] = "Tuesday" @schedule.should_not be_match end it "should match if weekday is ['Sun', 'Mon']" do @schedule[:weekday] = ["Sun", "Mon"] - @schedule.match?.should be_true + @schedule.match?.should be_truthy end it "should not match if weekday is ['Sun', 'Tue']" do @schedule[:weekday] = ["Sun", "Tue"] @schedule.should_not be_match end it "should match if the weekday is 'Monday'" do @schedule[:weekday] = "Monday" - @schedule.match?.should be_true + @schedule.match?.should be_truthy end it "should match if the weekday is 'Mon'" do @schedule[:weekday] = "Mon" - @schedule.match?.should be_true + @schedule.match?.should be_truthy end it "should match if the weekday is '1'" do @schedule[:weekday] = "1" - @schedule.match?.should be_true + @schedule.match?.should be_truthy end it "should not match if the weekday is Tuesday" do @schedule[:weekday] = "Tuesday" @schedule.should_not be_match end it "should match if weekday is ['Sun', 'Mon']" do @schedule[:weekday] = ["Sun", "Mon"] - @schedule.match?.should be_true + @schedule.match?.should be_truthy end end describe Puppet::Type.type(:schedule), "when matching days of week and ranges spanning days, day 1" do before do # Test with ranges and days-of-week both set. 2011-03-31 was a Thursday. Time.stubs(:now).returns(Time.local(2011, "mar", 31, 22, 30, 0)) end it "should match when the range and day of week matches" do @schedule[:range] = "22:00:00 - 02:00:00" @schedule[:weekday] = "Thursday" @schedule.must be_match end it "should not match when the range doesn't match even if the day-of-week matches" do @schedule[:range] = "23:30:00 - 21:00:00" @schedule[:weekday] = "Thursday" @schedule.must_not be_match end it "should not match when day-of-week doesn't match even if the range matches (1 day later)" do @schedule[:range] = "22:00:00 - 01:00:00" @schedule[:weekday] = "Friday" @schedule.must_not be_match end it "should not match when day-of-week doesn't match even if the range matches (1 day earlier)" do @schedule[:range] = "22:00:00 - 01:00:00" @schedule[:weekday] = "Wednesday" @schedule.must_not be_match end end describe Puppet::Type.type(:schedule), "when matching days of week and ranges spanning days, day 2" do before do # 2011-03-31 was a Thursday. As the end-time of a day spanning match, that means # we need to match on Wednesday. Time.stubs(:now).returns(Time.local(2011, "mar", 31, 1, 30, 0)) end it "should match when the range matches and the day of week should match" do @schedule[:range] = "22:00:00 - 02:00:00" @schedule[:weekday] = "Wednesday" @schedule.must be_match end it "should not match when the range does not match and the day of week should match" do @schedule[:range] = "22:00:00 - 01:00:00" @schedule[:weekday] = "Thursday" @schedule.must_not be_match end it "should not match when the range matches but the day-of-week does not (1 day later)" do @schedule[:range] = "22:00:00 - 02:00:00" @schedule[:weekday] = "Thursday" @schedule.must_not be_match end it "should not match when the range matches but the day-of-week does not (1 day later)" do @schedule[:range] = "22:00:00 - 02:00:00" @schedule[:weekday] = "Tuesday" @schedule.must_not be_match end end end diff --git a/spec/unit/type/user_spec.rb b/spec/unit/type/user_spec.rb index 974054309..64759b59f 100755 --- a/spec/unit/type/user_spec.rb +++ b/spec/unit/type/user_spec.rb @@ -1,519 +1,519 @@ #! /usr/bin/env ruby # encoding: UTF-8 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, :manages_shell 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 an 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 a manages_homedir feature" do described_class.provider_feature(:manages_homedir).should_not be_nil end it "should have a 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 it "should have a manages_shell feature" do described_class.provider_feature(:manages_shell).should_not be_nil end describe :managehome do let (:provider) { @provider_class.new(:name => 'foo', :ensure => :absent) } let (:instance) { described_class.new(:name => 'foo', :provider => provider) } it "defaults to false" do - instance[:managehome].should be_false + instance[:managehome].should be_falsey end it "can be set to false" do instance[:managehome] = 'false' end it "cannot be set to true for a provider that does not manage homedirs" do provider.class.stubs(:manages_homedir?).returns false expect { instance[:managehome] = 'yes' }.to raise_error(Puppet::Error, /can not manage home directories/) end it "can be set to true for a provider that does manage homedirs" do provider.class.stubs(:manages_homedir?).returns true instance[:managehome] = 'yes' end 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 expect { described_class.new(:name => 'foo', :ensure => :present) }.to_not raise_error end it "should support an :absent value" do 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 expect { described_class.new(:name => 'foo', :groups => 'bar') }.to_not raise_error end it "should support multiple groups as an array" do 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 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 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 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') }.to_not raise_error end it "should fail with an empty minimum age" do 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') }.to_not raise_error end it "should fail with an empty maximum age" do 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 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 expect { described_class.new(:name => 'foo', :password => :absent) }.to_not raise_error end end describe "when managing comment on Ruby 1.9", :if => String.method_defined?(:encode) do it "should force value encoding to ASCII-8BIT" do value = 'abcd™' value.encoding.should == Encoding::UTF_8 user = described_class.new(:name => 'foo', :comment => value) user[:comment].encoding.should == Encoding::ASCII_8BIT user[:comment].should == value.force_encoding(Encoding::ASCII_8BIT) end end describe "when manages_solaris_rbac is enabled" do it "should support a :role value for ensure" do 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 describe "when setting shell" do before :each do @shell_provider_class = described_class.provide(:shell_manager) do has_features :manages_shell mk_resource_methods def create; check_valid_shell;end def shell=(value); check_valid_shell; end def delete; end def exists?; get(:ensure) != :absent; end def flush; end def self.instances; []; end def check_valid_shell; end end described_class.stubs(:defaultprovider).returns @shell_provider_class end it "should call :check_valid_shell on the provider when changing shell value" do @provider = @shell_provider_class.new(:name => 'foo', :shell => '/bin/bash', :ensure => :present) @provider.expects(:check_valid_shell) resource = described_class.new(:name => 'foo', :shell => '/bin/zsh', :provider => @provider) Puppet::Util::Storage.stubs(:load) Puppet::Util::Storage.stubs(:store) catalog = Puppet::Resource::Catalog.new catalog.add_resource resource catalog.apply end it "should call :check_valid_shell on the provider when changing ensure from present to absent" do @provider = @shell_provider_class.new(:name => 'foo', :shell => '/bin/bash', :ensure => :absent) @provider.expects(:check_valid_shell) resource = described_class.new(:name => 'foo', :shell => '/bin/zsh', :provider => @provider) Puppet::Util::Storage.stubs(:load) Puppet::Util::Storage.stubs(:store) catalog = Puppet::Resource::Catalog.new catalog.add_resource resource catalog.apply end end describe "when purging ssh keys" do it "should not accept a keyfile with a relative path" do expect { described_class.new(:name => "a", :purge_ssh_keys => "keys") }.to raise_error(Puppet::Error, /Paths to keyfiles must be absolute, not keys/) end context "with a home directory specified" do it "should accept true" do described_class.new(:name => "a", :home => "/tmp", :purge_ssh_keys => true) end it "should accept the ~ wildcard" do described_class.new(:name => "a", :home => "/tmp", :purge_ssh_keys => "~/keys") end it "should accept the %h wildcard" do described_class.new(:name => "a", :home => "/tmp", :purge_ssh_keys => "%h/keys") end it "raises when given a relative path" do expect { described_class.new(:name => "a", :home => "/tmp", :purge_ssh_keys => "keys") }.to raise_error(Puppet::Error, /Paths to keyfiles must be absolute/) end end context "with no home directory specified" do it "should not accept true" do expect { described_class.new(:name => "a", :purge_ssh_keys => true) }.to raise_error(Puppet::Error, /purge_ssh_keys can only be true for users with a defined home directory/) end it "should not accept the ~ wildcard" do expect { described_class.new(:name => "a", :purge_ssh_keys => "~/keys") }.to raise_error(Puppet::Error, /meta character ~ or %h only allowed for users with a defined home directory/) end it "should not accept the %h wildcard" do expect { described_class.new(:name => "a", :purge_ssh_keys => "%h/keys") }.to raise_error(Puppet::Error, /meta character ~ or %h only allowed for users with a defined home directory/) end end context "with a valid parameter" do let(:paths) do [ "/dev/null", "/tmp/keyfile" ].map { |path| File.expand_path(path) } end subject do res = described_class.new(:name => "test", :purge_ssh_keys => paths) res.catalog = Puppet::Resource::Catalog.new res end it "should not just return from generate" do subject.expects :find_unmanaged_keys subject.generate end it "should check each keyfile for readability" do paths.each do |path| File.expects(:readable?).with(path) end subject.generate end end describe "generated keys" do subject do res = described_class.new(:name => "test_user_name", :purge_ssh_keys => purge_param) res.catalog = Puppet::Resource::Catalog.new res end context "when purging is disabled" do let(:purge_param) { false } its(:generate) { should be_empty } end context "when purging is enabled" do let(:purge_param) { my_fixture('authorized_keys') } let(:resources) { subject.generate } it "should contain a resource for each key" do names = resources.collect { |res| res.name } names.should include("key1 name") names.should include("keyname2") end it "should not include keys in comment lines" do names = resources.collect { |res| res.name } names.should_not include("keyname3") end it "should generate names for unnamed keys" do names = resources.collect { |res| res.name } fixture_path = File.join(my_fixture_dir, 'authorized_keys') names.should include("#{fixture_path}:unnamed-1") end it "should each have a value for the user property" do resources.map { |res| res[:user] }.reject { |user_name| user_name == "test_user_name" }.should be_empty end end end end end diff --git a/spec/unit/type/zone_spec.rb b/spec/unit/type/zone_spec.rb index 3497ae3a7..9f8b20142 100755 --- a/spec/unit/type/zone_spec.rb +++ b/spec/unit/type/zone_spec.rb @@ -1,172 +1,172 @@ #! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:zone) do let(:zone) { described_class.new(:name => 'dummy', :path => '/dummy', :provider => :solaris, :ip=>'if:1.2.3.4:2.3.4.5', :inherit=>'/', :dataset=>'tank') } let(:provider) { zone.provider } let(:ip) { zone.property(:ip) } let(:inherit) { zone.property(:inherit) } let(:dataset) { zone.property(:dataset) } parameters = [:create_args, :install_args, :sysidcfg, :realhostname] parameters.each do |parameter| it "should have a #{parameter} parameter" do described_class.attrclass(parameter).ancestors.should be_include(Puppet::Parameter) end end properties = [:ip, :iptype, :autoboot, :pool, :shares, :inherit, :path] properties.each do |property| it "should have a #{property} property" do described_class.attrclass(property).ancestors.should be_include(Puppet::Property) end end describe "when trying to set a property that is empty" do it "should verify that property.insync? of nil or :absent is true" do [inherit, ip, dataset].each do |prop| prop.stubs(:should).returns [] end [inherit, ip, dataset].each do |prop| - prop.insync?(nil).should be_true + prop.insync?(nil).should be_truthy end [inherit, ip, dataset].each do |prop| - prop.insync?(:absent).should be_true + prop.insync?(:absent).should be_truthy end end end describe "when trying to set a property that is non empty" do it "should verify that property.insync? of nil or :absent is false" do [inherit, ip, dataset].each do |prop| prop.stubs(:should).returns ['a','b'] end [inherit, ip, dataset].each do |prop| - prop.insync?(nil).should be_false + prop.insync?(nil).should be_falsey end [inherit, ip, dataset].each do |prop| - prop.insync?(:absent).should be_false + prop.insync?(:absent).should be_falsey end end end describe "when trying to set a property that is non empty" do it "insync? should return true or false depending on the current value, and new value" do [inherit, ip, dataset].each do |prop| prop.stubs(:should).returns ['a','b'] end [inherit, ip, dataset].each do |prop| - prop.insync?(['b', 'a']).should be_true + prop.insync?(['b', 'a']).should be_truthy end [inherit, ip, dataset].each do |prop| - prop.insync?(['a']).should be_false + prop.insync?(['a']).should be_falsey end end end it "should be valid when only :path is given" do described_class.new(:name => "dummy", :path => '/dummy', :provider => :solaris) end it "should be invalid when :ip is missing a \":\" and iptype is :shared" do expect { described_class.new(:name => "dummy", :ip => "if", :path => "/dummy", :provider => :solaris) }.to raise_error(Puppet::Error, /ip must contain interface name and ip address separated by a ":"/) end it "should be invalid when :ip has a \":\" and iptype is :exclusive" do expect { described_class.new(:name => "dummy", :ip => "if:1.2.3.4", :iptype => :exclusive, :provider => :solaris) }.to raise_error(Puppet::Error, /only interface may be specified when using exclusive IP stack/) end it "should be invalid when :ip has two \":\" and iptype is :exclusive" do expect { described_class.new(:name => "dummy", :ip => "if:1.2.3.4:2.3.4.5", :iptype => :exclusive, :provider => :solaris) }.to raise_error(Puppet::Error, /only interface may be specified when using exclusive IP stack/) end it "should be valid when :iptype is :shared and using interface and ip" do described_class.new(:name => "dummy", :path => "/dummy", :ip => "if:1.2.3.4", :provider => :solaris) end it "should be valid when :iptype is :shared and using interface, ip and default route" do described_class.new(:name => "dummy", :path => "/dummy", :ip => "if:1.2.3.4:2.3.4.5", :provider => :solaris) end it "should be valid when :iptype is :exclusive and using interface" do described_class.new(:name => "dummy", :path => "/dummy", :ip => "if", :iptype => :exclusive, :provider => :solaris) end it "should auto-require :dataset entries" do fs = 'random-pool/some-zfs' catalog = Puppet::Resource::Catalog.new relationship_graph = Puppet::Graph::RelationshipGraph.new(Puppet::Graph::RandomPrioritizer.new) zfs = Puppet::Type.type(:zfs).new(:name => fs) catalog.add_resource zfs zone = described_class.new(:name => "dummy", :path => "/foo", :ip => 'en1:1.0.0.0', :dataset => fs, :provider => :solaris) catalog.add_resource zone relationship_graph.populate_from(catalog) relationship_graph.dependencies(zone).should == [zfs] end describe Puppet::Zone::StateMachine do let (:sm) { Puppet::Zone::StateMachine.new } before :each do sm.insert_state :absent, :down => :destroy sm.insert_state :configured, :up => :configure, :down => :uninstall sm.insert_state :installed, :up => :install, :down => :stop sm.insert_state :running, :up => :start end context ":insert_state" do it "should insert state in correct order" do sm.insert_state :dummy, :left => :right sm.index(:dummy).should == 4 end end context ":alias_state" do it "should alias state" do sm.alias_state :dummy, :running sm.name(:dummy).should == :running end end context ":name" do it "should get an aliased state correctly" do sm.alias_state :dummy, :running sm.name(:dummy).should == :running end it "should get an un aliased state correctly" do sm.name(:dummy).should == :dummy end end context ":index" do it "should return the state index correctly" do sm.insert_state :dummy, :left => :right sm.index(:dummy).should == 4 end end context ":sequence" do it "should correctly return the actions to reach state specified" do sm.sequence(:absent, :running).map{|p|p[:up]}.should == [:configure,:install,:start] end it "should correctly return the actions to reach state specified(2)" do sm.sequence(:running, :absent).map{|p|p[:down]}.should == [:stop, :uninstall, :destroy] end end context ":cmp" do it "should correctly compare state sequence values" do sm.cmp?(:absent, :running).should == true sm.cmp?(:running, :running).should == false sm.cmp?(:running, :absent).should == false end end end end diff --git a/spec/unit/type/zpool_spec.rb b/spec/unit/type/zpool_spec.rb index e6d551cff..abe7063e5 100755 --- a/spec/unit/type/zpool_spec.rb +++ b/spec/unit/type/zpool_spec.rb @@ -1,109 +1,109 @@ #! /usr/bin/env ruby require 'spec_helper' zpool = Puppet::Type.type(:zpool) describe zpool do before do @provider = stub 'provider' @resource = stub 'resource', :resource => nil, :provider => @provider, :line => nil, :file => nil end properties = [:ensure, :disk, :mirror, :raidz, :spare, :log] properties.each do |property| it "should have a #{property} property" do zpool.attrclass(property).ancestors.should be_include(Puppet::Property) end end parameters = [:pool, :raid_parity] parameters.each do |parameter| it "should have a #{parameter} parameter" do zpool.attrclass(parameter).ancestors.should be_include(Puppet::Parameter) end end end vdev_property = Puppet::Property::VDev describe vdev_property do before do vdev_property.initvars @resource = stub 'resource', :[]= => nil, :property => nil @property = vdev_property.new(:resource => @resource) end it "should be insync if the devices are the same" do @property.should = ["dev1 dev2"] - @property.safe_insync?(["dev2 dev1"]).must be_true + @property.safe_insync?(["dev2 dev1"]).must be_truthy end it "should be out of sync if the devices are not the same" do @property.should = ["dev1 dev3"] - @property.safe_insync?(["dev2 dev1"]).must be_false + @property.safe_insync?(["dev2 dev1"]).must be_falsey end it "should be insync if the devices are the same and the should values are comma separated" do @property.should = ["dev1", "dev2"] - @property.safe_insync?(["dev2 dev1"]).must be_true + @property.safe_insync?(["dev2 dev1"]).must be_truthy end it "should be out of sync if the device is absent and should has a value" do @property.should = ["dev1", "dev2"] - @property.safe_insync?(:absent).must be_false + @property.safe_insync?(:absent).must be_falsey end it "should be insync if the device is absent and should is absent" do @property.should = [:absent] - @property.safe_insync?(:absent).must be_true + @property.safe_insync?(:absent).must be_truthy end end multi_vdev_property = Puppet::Property::MultiVDev describe multi_vdev_property do before do multi_vdev_property.initvars @resource = stub 'resource', :[]= => nil, :property => nil @property = multi_vdev_property.new(:resource => @resource) end it "should be insync if the devices are the same" do @property.should = ["dev1 dev2"] - @property.safe_insync?(["dev2 dev1"]).must be_true + @property.safe_insync?(["dev2 dev1"]).must be_truthy end it "should be out of sync if the devices are not the same" do @property.should = ["dev1 dev3"] - @property.safe_insync?(["dev2 dev1"]).must be_false + @property.safe_insync?(["dev2 dev1"]).must be_falsey end it "should be out of sync if the device is absent and should has a value" do @property.should = ["dev1", "dev2"] - @property.safe_insync?(:absent).must be_false + @property.safe_insync?(:absent).must be_falsey end it "should be insync if the device is absent and should is absent" do @property.should = [:absent] - @property.safe_insync?(:absent).must be_true + @property.safe_insync?(:absent).must be_truthy end describe "when there are multiple lists of devices" do it "should be in sync if each group has the same devices" do @property.should = ["dev1 dev2", "dev3 dev4"] - @property.safe_insync?(["dev2 dev1", "dev3 dev4"]).must be_true + @property.safe_insync?(["dev2 dev1", "dev3 dev4"]).must be_truthy end it "should be out of sync if any group has the different devices" do @property.should = ["dev1 devX", "dev3 dev4"] - @property.safe_insync?(["dev2 dev1", "dev3 dev4"]).must be_false + @property.safe_insync?(["dev2 dev1", "dev3 dev4"]).must be_falsey end it "should be out of sync if devices are in the wrong group" do @property.should = ["dev1 dev2", "dev3 dev4"] - @property.safe_insync?(["dev2 dev3", "dev1 dev4"]).must be_false + @property.safe_insync?(["dev2 dev3", "dev1 dev4"]).must be_falsey end end end diff --git a/spec/unit/type_spec.rb b/spec/unit/type_spec.rb index 9b9b73172..9a0f76897 100755 --- a/spec/unit/type_spec.rb +++ b/spec/unit/type_spec.rb @@ -1,1185 +1,1185 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/compiler' describe Puppet::Type, :unless => Puppet.features.microsoft_windows? do include PuppetSpec::Files include PuppetSpec::Compiler 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(:name) 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 "can retrieve all set parameters" do resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present, :tag => 'foo') params = resource.parameters_with_value [:name, :provider, :ensure, :fstype, :pass, :dump, :target, :loglevel, :tag].each do |name| params.should be_include(resource.parameter(name)) end end it "can not return any `nil` values when retrieving all set parameters" do resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present, :tag => 'foo') params = resource.parameters_with_value params.should_not be_include(nil) end it "can return an iterator for all set parameters" do resource = Puppet::Type.type(:notify).new(:name=>'foo',:message=>'bar',:tag=>'baz',:require=> "File['foo']") params = [:name, :message, :withpath, :loglevel, :tag, :require] resource.eachparameter { |param| params.should be_include(param.to_s.to_sym) } 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 zero if it has no catalog" do Puppet::Type.type(:mount).new(:name => "foo").version.should == 0 end it "reports the correct path even after path is used during setup of the type" do Puppet::Type.newtype(:testing) do newparam(:name) do isnamevar validate do |value| path # forces the computation of the path end end end ral = compile_to_ral(<<-MANIFEST) class something { testing { something: } } include something MANIFEST ral.resource("Testing[something]").path.should == "/Stage[main]/Something/Testing[something]" end context "alias metaparam" do it "creates a new name that can be used for resource references" do ral = compile_to_ral(<<-MANIFEST) notify { a: alias => c } MANIFEST expect(ral.resource("Notify[a]")).to eq(ral.resource("Notify[c]")) end end context "resource attributes" do let(:resource) { resource = Puppet::Type.type(:mount).new(:name => "foo") catalog = Puppet::Resource::Catalog.new catalog.version = 50 catalog.add_resource resource resource } it "should consider its version to be its catalog version" do resource.version.should == 50 end it "should have tags" do expect(resource).to be_tagged("mount") expect(resource).to be_tagged("foo") end it "should have a path" do resource.path.should == "/Mount[foo]" end 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}.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 set the tags" do @resource.tag("abc", "def") @resource.event.should be_tagged("abc") @resource.event.should be_tagged("def") 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 + @type.new(:name => "foo", :audit => 'all').managed?.should be_falsey 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 + @type.new(:name => "foo", :foo => 'did someone say food?').managed?.should be_falsey 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 + @type.new(:name => "foo", :bar => 'Let us all go there').managed?.should be_truthy 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 describe "with a parent class from another type" do before :each do @parent_type = Puppet::Type.newtype(:provider_parent_type) do newparam(:name) { isnamevar } end @parent_provider = @parent_type.provide(:parent_provider) end it "should be created successfully" do child_provider = @type.provide(:child_provider, :parent => @parent_provider) child_provider.ancestors.should include(@parent_provider) end it "should be registered as a provider of the child type" do child_provider = @type.provide(:child_provider, :parent => @parent_provider) @type.providers.should include(:child_provider) @parent_type.providers.should_not include(:child_provider) end 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 context "autorelations" do before :each do type = Puppet::Type.newtype(:autorelation_one) do newparam(:name) { isnamevar } end end describe "when building autorelations" do it "should be able to autorequire resources" do type = Puppet::Type.newtype(:autorelation_two) do newparam(:name) { isnamevar } autorequire(:autorelation_one) { ['foo'] } end relationship_graph = compile_to_relationship_graph(<<-MANIFEST) autorelation_one { 'foo': } autorelation_two { 'bar': } MANIFEST src = relationship_graph.vertices.select{ |x| x.ref.to_s == 'Autorelation_one[foo]' }.first dst = relationship_graph.vertices.select{ |x| x.ref.to_s == 'Autorelation_two[bar]' }.first - relationship_graph.edge?(src,dst).should be_true + relationship_graph.edge?(src,dst).should be_truthy relationship_graph.edges_between(src,dst).first.event.should == :NONE end it "should be able to autosubscribe resources" do type = Puppet::Type.newtype(:autorelation_two) do newparam(:name) { isnamevar } autosubscribe(:autorelation_one) { ['foo'] } end relationship_graph = compile_to_relationship_graph(<<-MANIFEST) autorelation_one { 'foo': } autorelation_two { 'bar': } MANIFEST src = relationship_graph.vertices.select{ |x| x.ref.to_s == 'Autorelation_one[foo]' }.first dst = relationship_graph.vertices.select{ |x| x.ref.to_s == 'Autorelation_two[bar]' }.first - relationship_graph.edge?(src,dst).should be_true + relationship_graph.edge?(src,dst).should be_truthy relationship_graph.edges_between(src,dst).first.event.should == :ALL_EVENTS end it "should be able to autobefore resources" do type = Puppet::Type.newtype(:autorelation_two) do newparam(:name) { isnamevar } autobefore(:autorelation_one) { ['foo'] } end relationship_graph = compile_to_relationship_graph(<<-MANIFEST) autorelation_one { 'foo': } autorelation_two { 'bar': } MANIFEST src = relationship_graph.vertices.select{ |x| x.ref.to_s == 'Autorelation_two[bar]' }.first dst = relationship_graph.vertices.select{ |x| x.ref.to_s == 'Autorelation_one[foo]' }.first - relationship_graph.edge?(src,dst).should be_true + relationship_graph.edge?(src,dst).should be_truthy relationship_graph.edges_between(src,dst).first.event.should == :NONE end it "should be able to autonotify resources" do type = Puppet::Type.newtype(:autorelation_two) do newparam(:name) { isnamevar } autonotify(:autorelation_one) { ['foo'] } end relationship_graph = compile_to_relationship_graph(<<-MANIFEST) autorelation_one { 'foo': } autorelation_two { 'bar': } MANIFEST src = relationship_graph.vertices.select{ |x| x.ref.to_s == 'Autorelation_two[bar]' }.first dst = relationship_graph.vertices.select{ |x| x.ref.to_s == 'Autorelation_one[foo]' }.first - relationship_graph.edge?(src,dst).should be_true + relationship_graph.edge?(src,dst).should be_truthy relationship_graph.edges_between(src,dst).first.event.should == :ALL_EVENTS end end end describe "when initializing" do 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 => :yes, :fstype => "boo"}) params = Puppet::Type.type(:mount).new(resource).to_hash params[:fstype].should == "boo" params[:atboot].should == :yes 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 => :yes, :fstype => "boo") resource[:fstype].must == "boo" resource[:atboot].must == :yes end end it "should fail if any invalid attributes have been provided" do expect { Puppet::Type.type(:mount).new(:title => "/foo", :nosuchattr => "whatever") }.to raise_error(Puppet::Error, /Invalid parameter/) end context "when an attribute fails validation" do it "should fail with Puppet::ResourceError when PuppetError raised" do expect { Puppet::Type.type(:file).new(:title => "/foo", :source => "unknown:///") }.to raise_error(Puppet::ResourceError, /Parameter source failed on File\[.*foo\]/) end it "should fail with Puppet::ResourceError when ArgumentError raised" do expect { Puppet::Type.type(:file).new(:title => "/foo", :mode => "abcdef") }.to raise_error(Puppet::ResourceError, /Parameter mode failed on File\[.*foo\]/) end it "should include the file/line in the error" do Puppet::Type.type(:file).any_instance.stubs(:file).returns("example.pp") Puppet::Type.type(:file).any_instance.stubs(:line).returns(42) expect { Puppet::Type.type(:file).new(:title => "/foo", :source => "unknown:///") }.to raise_error(Puppet::ResourceError, /example.pp:42/) end 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 expect { Puppet::Type.type(:mount).new(:atboot => :yes) }.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 => :yes, :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 => :yes}) 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 => :yes, :noop => false).original_parameters.should == {:atboot => :yes, :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 context "when validating the resource" do it "should call the type's validate method if present" do Puppet::Type.type(:file).any_instance.expects(:validate) Puppet::Type.type(:file).new(:name => make_absolute('/foo')) end it "should raise Puppet::ResourceError with resource name when Puppet::Error raised" do expect do Puppet::Type.type(:file).new( :name => make_absolute('/foo'), :source => "puppet:///", :content => "foo" ) end.to raise_error(Puppet::ResourceError, /Validation of File\[.*foo.*\]/) end it "should raise Puppet::ResourceError with manifest file and line on failure" do Puppet::Type.type(:file).any_instance.stubs(:file).returns("example.pp") Puppet::Type.type(:file).any_instance.stubs(:line).returns(42) expect do Puppet::Type.type(:file).new( :name => make_absolute('/foo'), :source => "puppet:///", :content => "foo" ) end.to raise_error(Puppet::ResourceError, /Validation.*example.pp:42/) end end end describe "when #finish is called on a type" do let(:post_hook_type) do Puppet::Type.newtype(:finish_test) do newparam(:name) { isnamevar } newparam(:post) do def post_compile raise "post_compile hook ran" end end end end let(:post_hook_resource) do post_hook_type.new(:name => 'foo',:post => 'fake_value') end it "should call #post_compile on parameters that implement it" do expect { post_hook_resource.finish }.to raise_error(RuntimeError, "post_compile hook ran") 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 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] = "myname" @resource.title = "other name" @resource.retrieve_resource[:name].should == "myname" 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 "#to_resource" do it "should return a Puppet::Resource that includes properties, parameters and tags" do type_resource = Puppet::Type.type(:mount).new( :ensure => :present, :name => "foo", :fstype => "bar", :remounts => true ) type_resource.tags = %w{bar baz} # If it's not a property it's a parameter type_resource.parameters[:remounts].should_not be_a(Puppet::Property) - type_resource.parameters[:fstype].is_a?(Puppet::Property).should be_true + type_resource.parameters[:fstype].is_a?(Puppet::Property).should be_truthy type_resource.property(:ensure).expects(:retrieve).returns :present type_resource.property(:fstype).expects(:retrieve).returns 15 resource = type_resource.to_resource resource.should be_a Puppet::Resource resource[:fstype].should == 15 resource[:remounts].should == :true resource.tags.should == Puppet::Util::TagSet.new(%w{foo bar baz mount}) 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 after :each do Puppet::Type.rmtype(:type_spec_fake_type) end let :type do Puppet::Type.newtype(:type_spec_fake_type) do newparam(:name) do isnamevar end newproperty(:prop1) {} end Puppet::Type.type(:type_spec_fake_type) end it "should not fail if no suitable providers are found" do type.provide(:fake1) do confine :exists => '/no/such/file' mk_resource_methods end expect { type.instances.should == [] }.to_not raise_error end context "with a default provider" do before :each do type.provide(:default) do defaultfor :operatingsystem => Facter.value(:operatingsystem) mk_resource_methods class << self attr_accessor :names end def self.instance(name) new(:name => name, :ensure => :present) end def self.instances @instances ||= names.collect { |name| instance(name.to_s) } end @names = [:one, :two] end end it "should return only instances of the type" do type.instances.should be_all {|x| x.is_a? type } end it "should return instances from the default provider" do type.instances.map(&:name).should == ["one", "two"] end it "should return instances from all providers" do type.provide(:fake1, :parent => :default) { @names = [:three, :four] } type.instances.map(&:name).should == ["one", "two", "three", "four"] end it "should not return instances from unsuitable providers" do type.provide(:fake1, :parent => :default) do @names = [:three, :four] confine :exists => "/no/such/file" end type.instances.map(&:name).should == ["one", "two"] end 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" do before do @path = File.expand_path('/foo') @resource = Puppet::Type.type(:file).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 describe 'if any specified resource is not in the catalog' do let(:catalog) { mock 'catalog' } let(:resource) do stub 'resource', :catalog => catalog, :ref => 'resource', :line= => nil, :file= => nil end let(:param) { Puppet::Type.metaparamclass(:require).new(:resource => resource, :value => %w{Foo[bar] Class[test]}) } before do catalog.expects(:resource).with("Foo[bar]").returns "something" catalog.expects(:resource).with("Class[Test]").returns nil end describe "and the resource doesn't have a file or line number" do it "raises an error" do expect { param.validate_relationship }.to raise_error do |error| error.should be_a Puppet::ResourceError error.message.should match %r[Class\[Test\]] end end end describe "and the resource has a file or line number" do before do resource.stubs(:line).returns '42' resource.stubs(:file).returns '/hitchhikers/guide/to/the/galaxy' end it "raises an error with context" do expect { param.validate_relationship }.to raise_error do |error| error.should be_a Puppet::ResourceError error.message.should match %r[Class\[Test\]] error.message.should match %r["in /hitchhikers/guide/to/the/galaxy:42"] end end end 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 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 context "type attribute bracket methods" do after :each do Puppet::Type.rmtype(:attributes) end let :type do Puppet::Type.newtype(:attributes) do newparam(:name) {} end end it "should work with parameters" do type.newparam(:param) {} instance = type.new(:name => 'test') expect { instance[:param] = true }.to_not raise_error expect { instance["param"] = true }.to_not raise_error instance[:param].should == true instance["param"].should == true end it "should work with meta-parameters" do instance = type.new(:name => 'test') expect { instance[:noop] = true }.to_not raise_error expect { instance["noop"] = true }.to_not raise_error instance[:noop].should == true instance["noop"].should == true end it "should work with properties" do type.newproperty(:property) {} instance = type.new(:name => 'test') expect { instance[:property] = true }.to_not raise_error expect { instance["property"] = true }.to_not raise_error instance.property(:property).must be - instance.should(:property).must be_true + instance.should(:property).must be_truthy end it "should handle proprieties correctly" do # Order of assignment is significant in this test. props = {} [:one, :two, :three].each {|prop| type.newproperty(prop) {} } instance = type.new(:name => "test") instance[:one] = "boo" one = instance.property(:one) instance.properties.must == [one] instance[:three] = "rah" three = instance.property(:three) instance.properties.must == [one, three] instance[:two] = "whee" two = instance.property(:two) instance.properties.must == [one, two, three] end it "newattr should handle required features correctly" do Puppet::Util::Log.level = :debug type.feature :feature1, "one" type.feature :feature2, "two" none = type.newproperty(:none) {} one = type.newproperty(:one, :required_features => :feature1) {} two = type.newproperty(:two, :required_features => [:feature1, :feature2]) {} nope = type.provide(:nope) {} maybe = type.provide(:maybe) { has_features :feature1 } yep = type.provide(:yep) { has_features :feature1, :feature2 } [nope, maybe, yep].each_with_index do |provider, i| rsrc = type.new(:provider => provider.name, :name => "test#{i}", :none => "a", :one => "b", :two => "c") rsrc.should(:none).must be if provider.declared_feature? :feature1 rsrc.should(:one).must be else rsrc.should(:one).must_not be @logs.find {|l| l.message =~ /not managing attribute one/ }.should be end if provider.declared_feature? :feature2 rsrc.should(:two).must be else rsrc.should(:two).must_not be @logs.find {|l| l.message =~ /not managing attribute two/ }.should be end end end end end diff --git a/spec/unit/util/backups_spec.rb b/spec/unit/util/backups_spec.rb index ce7b9b756..70472f012 100755 --- a/spec/unit/util/backups_spec.rb +++ b/spec/unit/util/backups_spec.rb @@ -1,134 +1,134 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/backups' describe Puppet::Util::Backups do include PuppetSpec::Files let(:bucket) { stub('bucket', :name => "foo") } let!(:file) do f = Puppet::Type.type(:file).new(:name => path, :backup => 'foo') f.stubs(:bucket).returns(bucket) f end describe "when backing up a file" do let(:path) { make_absolute('/no/such/file') } it "should noop if the file does not exist" do file = Puppet::Type.type(:file).new(:name => path) file.expects(:bucket).never Puppet::FileSystem.expects(:exist?).with(path).returns false file.perform_backup end it "should succeed silently if self[:backup] is false" do file = Puppet::Type.type(:file).new(:name => path, :backup => false) file.expects(:bucket).never Puppet::FileSystem.expects(:exist?).never file.perform_backup end it "a bucket should be used when provided" do lstat_path_as(path, 'file') bucket.expects(:backup).with(path).returns("mysum") Puppet::FileSystem.expects(:exist?).with(path).returns(true) file.perform_backup end it "should propagate any exceptions encountered when backing up to a filebucket" do lstat_path_as(path, 'file') bucket.expects(:backup).raises ArgumentError Puppet::FileSystem.expects(:exist?).with(path).returns(true) lambda { file.perform_backup }.should raise_error(ArgumentError) end describe "and local backup is configured" do let(:ext) { 'foobkp' } let(:backup) { path + '.' + ext } let(:file) { Puppet::Type.type(:file).new(:name => path, :backup => '.'+ext) } it "should remove any local backup if one exists" do lstat_path_as(backup, 'file') Puppet::FileSystem.expects(:unlink).with(backup) FileUtils.stubs(:cp_r) Puppet::FileSystem.expects(:exist?).with(path).returns(true) file.perform_backup end it "should fail when the old backup can't be removed" do lstat_path_as(backup, 'file') Puppet::FileSystem.expects(:unlink).with(backup).raises ArgumentError FileUtils.expects(:cp_r).never Puppet::FileSystem.expects(:exist?).with(path).returns(true) lambda { file.perform_backup }.should raise_error(Puppet::Error) end it "should not try to remove backups that don't exist" do Puppet::FileSystem.expects(:lstat).with(backup).raises(Errno::ENOENT) Puppet::FileSystem.expects(:unlink).with(backup).never FileUtils.stubs(:cp_r) Puppet::FileSystem.expects(:exist?).with(path).returns(true) file.perform_backup end it "a copy should be created in the local directory" do FileUtils.expects(:cp_r).with(path, backup, :preserve => true) Puppet::FileSystem.stubs(:exist?).with(path).returns(true) - file.perform_backup.should be_true + file.perform_backup.should be_truthy end it "should propagate exceptions if no backup can be created" do FileUtils.expects(:cp_r).raises ArgumentError Puppet::FileSystem.stubs(:exist?).with(path).returns(true) lambda { file.perform_backup }.should raise_error(Puppet::Error) end end end describe "when backing up a directory" do let(:path) { make_absolute('/my/dir') } let(:filename) { File.join(path, 'file') } it "a bucket should work when provided" do File.stubs(:file?).with(filename).returns true Find.expects(:find).with(path).yields(filename) bucket.expects(:backup).with(filename).returns true lstat_path_as(path, 'directory') Puppet::FileSystem.stubs(:exist?).with(path).returns(true) Puppet::FileSystem.stubs(:exist?).with(filename).returns(true) file.perform_backup end it "should do nothing when recursing" do file = Puppet::Type.type(:file).new(:name => path, :backup => 'foo', :recurse => true) bucket.expects(:backup).never stub_file = stub('file', :stat => stub('stat', :ftype => 'directory')) Puppet::FileSystem.stubs(:new).with(path).returns stub_file Find.expects(:find).never file.perform_backup end end def lstat_path_as(path, ftype) Puppet::FileSystem.expects(:lstat).with(path).returns(stub('File::Stat', :ftype => ftype)) end end diff --git a/spec/unit/util/colors_spec.rb b/spec/unit/util/colors_spec.rb index b0f791f82..8eaf0c600 100755 --- a/spec/unit/util/colors_spec.rb +++ b/spec/unit/util/colors_spec.rb @@ -1,89 +1,89 @@ #!/usr/bin/env ruby require 'spec_helper' describe Puppet::Util::Colors do include Puppet::Util::Colors let (:message) { 'a message' } let (:color) { :black } let (:subject) { self } describe ".console_color" do it { should respond_to :console_color } it "should generate ANSI escape sequences" do subject.console_color(color, message).should == "\e[0;30m#{message}\e[0m" end end describe ".html_color" do it { should respond_to :html_color } it "should generate an HTML span element and style attribute" do subject.html_color(color, message).should =~ /#{message}<\/span>/ end end describe ".colorize" do it { should respond_to :colorize } context "ansicolor supported" do before :each do subject.stubs(:console_has_color?).returns(true) end it "should colorize console output" do Puppet[:color] = true subject.expects(:console_color).with(color, message) subject.colorize(:black, message) end it "should not colorize unknown color schemes" do Puppet[:color] = :thisisanunknownscheme subject.colorize(:black, message).should == message end end context "ansicolor not supported" do before :each do subject.stubs(:console_has_color?).returns(false) end it "should not colorize console output" do Puppet[:color] = true subject.expects(:console_color).never subject.colorize(:black, message).should == message end it "should colorize html output" do Puppet[:color] = :html subject.expects(:html_color).with(color, message) subject.colorize(color, message) end end end context "on Windows in Ruby 1.x", :if => Puppet.features.microsoft_windows? && RUBY_VERSION =~ /^1./ do it "should define WideConsole" do - expect(defined?(Puppet::Util::Colors::WideConsole)).to be_true + expect(defined?(Puppet::Util::Colors::WideConsole)).to be_truthy end it "should define WideIO" do - expect(defined?(Puppet::Util::Colors::WideIO)).to be_true + expect(defined?(Puppet::Util::Colors::WideIO)).to be_truthy end end context "on Windows in Ruby 2.x", :if => Puppet.features.microsoft_windows? && RUBY_VERSION =~ /^2./ do it "should not define WideConsole" do - expect(defined?(Puppet::Util::Colors::WideConsole)).to be_false + expect(defined?(Puppet::Util::Colors::WideConsole)).to be_falsey end it "should not define WideIO" do - expect(defined?(Puppet::Util::Colors::WideIO)).to be_false + expect(defined?(Puppet::Util::Colors::WideIO)).to be_falsey end end end diff --git a/spec/unit/util/execution_spec.rb b/spec/unit/util/execution_spec.rb index 9e3111ac3..fa2062eb9 100755 --- a/spec/unit/util/execution_spec.rb +++ b/spec/unit/util/execution_spec.rb @@ -1,675 +1,675 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/file_system/uniquefile' describe Puppet::Util::Execution do include Puppet::Util::Execution # utility methods to help us test some private methods without being quite so verbose def call_exec_posix(command, arguments, stdin, stdout, stderr) Puppet::Util::Execution.send(:execute_posix, command, arguments, stdin, stdout, stderr) end def call_exec_windows(command, arguments, stdin, stdout, stderr) Puppet::Util::Execution.send(:execute_windows, command, arguments, stdin, stdout, stderr) end describe "execution methods" do let(:pid) { 5501 } let(:process_handle) { 0xDEADBEEF } let(:thread_handle) { 0xCAFEBEEF } let(:proc_info_stub) { stub 'processinfo', :process_handle => process_handle, :thread_handle => thread_handle, :process_id => pid} let(:null_file) { Puppet.features.microsoft_windows? ? 'NUL' : '/dev/null' } def stub_process_wait(exitstatus) if Puppet.features.microsoft_windows? Puppet::Util::Windows::Process.stubs(:wait_process).with(process_handle).returns(exitstatus) FFI::WIN32.stubs(:CloseHandle).with(process_handle) FFI::WIN32.stubs(:CloseHandle).with(thread_handle) else Process.stubs(:waitpid2).with(pid).returns([pid, stub('child_status', :exitstatus => exitstatus)]) end end describe "#execute_posix (stubs)", :unless => Puppet.features.microsoft_windows? do before :each do # Most of the things this method does are bad to do during specs. :/ Kernel.stubs(:fork).returns(pid).yields Process.stubs(:setsid) Kernel.stubs(:exec) Puppet::Util::SUIDManager.stubs(:change_user) Puppet::Util::SUIDManager.stubs(:change_group) # ensure that we don't really close anything! (0..256).each {|n| IO.stubs(:new) } $stdin.stubs(:reopen) $stdout.stubs(:reopen) $stderr.stubs(:reopen) @stdin = File.open(null_file, 'r') @stdout = Puppet::FileSystem::Uniquefile.new('stdout') @stderr = File.open(null_file, 'w') # there is a danger here that ENV will be modified by exec_posix. Normally it would only affect the ENV # of a forked process, but here, we're stubbing Kernel.fork, so the method has the ability to override the # "real" ENV. To guard against this, we'll capture a snapshot of ENV before each test. @saved_env = ENV.to_hash # Now, we're going to effectively "mock" the magic ruby 'ENV' variable by creating a local definition of it # inside of the module we're testing. Puppet::Util::Execution::ENV = {} end after :each do # And here we remove our "mock" version of 'ENV', which will allow us to validate that the real ENV has been # left unharmed. Puppet::Util::Execution.send(:remove_const, :ENV) # capture the current environment and make sure it's the same as it was before the test cur_env = ENV.to_hash # we will get some fairly useless output if we just use the raw == operator on the hashes here, so we'll # be a bit more explicit and laborious in the name of making the error more useful... @saved_env.each_pair { |key,val| cur_env[key].should == val } (cur_env.keys - @saved_env.keys).should == [] end it "should fork a child process to execute the command" do Kernel.expects(:fork).returns(pid).yields Kernel.expects(:exec).with('test command') call_exec_posix('test command', {}, @stdin, @stdout, @stderr) end it "should start a new session group" do Process.expects(:setsid) call_exec_posix('test command', {}, @stdin, @stdout, @stderr) end it "should permanently change to the correct user and group if specified" do Puppet::Util::SUIDManager.expects(:change_group).with(55, true) Puppet::Util::SUIDManager.expects(:change_user).with(50, true) call_exec_posix('test command', {:uid => 50, :gid => 55}, @stdin, @stdout, @stderr) end it "should exit failure if there is a problem execing the command" do Kernel.expects(:exec).with('test command').raises("failed to execute!") Puppet::Util::Execution.stubs(:puts) Puppet::Util::Execution.expects(:exit!).with(1) call_exec_posix('test command', {}, @stdin, @stdout, @stderr) end it "should properly execute commands specified as arrays" do Kernel.expects(:exec).with('test command', 'with', 'arguments') call_exec_posix(['test command', 'with', 'arguments'], {:uid => 50, :gid => 55}, @stdin, @stdout, @stderr) end it "should properly execute string commands with embedded newlines" do Kernel.expects(:exec).with("/bin/echo 'foo' ; \n /bin/echo 'bar' ;") call_exec_posix("/bin/echo 'foo' ; \n /bin/echo 'bar' ;", {:uid => 50, :gid => 55}, @stdin, @stdout, @stderr) end it "should return the pid of the child process" do call_exec_posix('test command', {}, @stdin, @stdout, @stderr).should == pid end end describe "#execute_windows (stubs)", :if => Puppet.features.microsoft_windows? do before :each do Process.stubs(:create).returns(proc_info_stub) stub_process_wait(0) @stdin = File.open(null_file, 'r') @stdout = Puppet::FileSystem::Uniquefile.new('stdout') @stderr = File.open(null_file, 'w') end it "should create a new process for the command" do Process.expects(:create).with( :command_line => "test command", :startup_info => {:stdin => @stdin, :stdout => @stdout, :stderr => @stderr}, :close_handles => false ).returns(proc_info_stub) call_exec_windows('test command', {}, @stdin, @stdout, @stderr) end it "should return the process info of the child process" do call_exec_windows('test command', {}, @stdin, @stdout, @stderr).should == proc_info_stub end it "should quote arguments containing spaces if command is specified as an array" do Process.expects(:create).with do |args| args[:command_line] == '"test command" with some "arguments \"with spaces"' end.returns(proc_info_stub) call_exec_windows(['test command', 'with', 'some', 'arguments "with spaces'], {}, @stdin, @stdout, @stderr) end end describe "#execute (stubs)" do before :each do stub_process_wait(0) end describe "when an execution stub is specified" do before :each do Puppet::Util::ExecutionStub.set do |command,args,stdin,stdout,stderr| "execution stub output" end end it "should call the block on the stub" do Puppet::Util::Execution.execute("/usr/bin/run_my_execute_stub").should == "execution stub output" end it "should not actually execute anything" do Puppet::Util::Execution.expects(:execute_posix).never Puppet::Util::Execution.expects(:execute_windows).never Puppet::Util::Execution.execute("/usr/bin/run_my_execute_stub") end end describe "when setting up input and output files" do include PuppetSpec::Files let(:executor) { Puppet.features.microsoft_windows? ? 'execute_windows' : 'execute_posix' } let(:rval) { Puppet.features.microsoft_windows? ? proc_info_stub : pid } before :each do Puppet::Util::Execution.stubs(:wait_for_output) end it "should set stdin to the stdinfile if specified" do input = tmpfile('stdin') FileUtils.touch(input) Puppet::Util::Execution.expects(executor).with do |_,_,stdin,_,_| stdin.path == input end.returns(rval) Puppet::Util::Execution.execute('test command', :stdinfile => input) end it "should set stdin to the null file if not specified" do Puppet::Util::Execution.expects(executor).with do |_,_,stdin,_,_| stdin.path == null_file end.returns(rval) Puppet::Util::Execution.execute('test command') end describe "when squelch is set" do it "should set stdout and stderr to the null file" do Puppet::Util::Execution.expects(executor).with do |_,_,_,stdout,stderr| stdout.path == null_file and stderr.path == null_file end.returns(rval) Puppet::Util::Execution.execute('test command', :squelch => true) end end describe "when squelch is not set" do it "should set stdout to a temporary output file" do outfile = Puppet::FileSystem::Uniquefile.new('stdout') Puppet::FileSystem::Uniquefile.stubs(:new).returns(outfile) Puppet::Util::Execution.expects(executor).with do |_,_,_,stdout,_| stdout.path == outfile.path end.returns(rval) Puppet::Util::Execution.execute('test command', :squelch => false) end it "should set stderr to the same file as stdout if combine is true" do outfile = Puppet::FileSystem::Uniquefile.new('stdout') Puppet::FileSystem::Uniquefile.stubs(:new).returns(outfile) Puppet::Util::Execution.expects(executor).with do |_,_,_,stdout,stderr| stdout.path == outfile.path and stderr.path == outfile.path end.returns(rval) Puppet::Util::Execution.execute('test command', :squelch => false, :combine => true) end it "should set stderr to the null device if combine is false" do outfile = Puppet::FileSystem::Uniquefile.new('stdout') Puppet::FileSystem::Uniquefile.stubs(:new).returns(outfile) Puppet::Util::Execution.expects(executor).with do |_,_,_,stdout,stderr| stdout.path == outfile.path and stderr.path == null_file end.returns(rval) Puppet::Util::Execution.execute('test command', :squelch => false, :combine => false) end it "should combine stdout and stderr if combine is true" do outfile = Puppet::FileSystem::Uniquefile.new('stdout') Puppet::FileSystem::Uniquefile.stubs(:new).returns(outfile) Puppet::Util::Execution.expects(executor).with do |_,_,_,stdout,stderr| stdout.path == outfile.path and stderr.path == outfile.path end.returns(rval) Puppet::Util::Execution.execute('test command', :combine => true) end it "should default combine to true when no options are specified" do outfile = Puppet::FileSystem::Uniquefile.new('stdout') Puppet::FileSystem::Uniquefile.stubs(:new).returns(outfile) Puppet::Util::Execution.expects(executor).with do |_,_,_,stdout,stderr| stdout.path == outfile.path and stderr.path == outfile.path end.returns(rval) Puppet::Util::Execution.execute('test command') end it "should default combine to false when options are specified, but combine is not" do outfile = Puppet::FileSystem::Uniquefile.new('stdout') Puppet::FileSystem::Uniquefile.stubs(:new).returns(outfile) Puppet::Util::Execution.expects(executor).with do |_,_,_,stdout,stderr| stdout.path == outfile.path and stderr.path == null_file end.returns(rval) Puppet::Util::Execution.execute('test command', :failonfail => false) end it "should default combine to false when an empty hash of options is specified" do outfile = Puppet::FileSystem::Uniquefile.new('stdout') Puppet::FileSystem::Uniquefile.stubs(:new).returns(outfile) Puppet::Util::Execution.expects(executor).with do |_,_,_,stdout,stderr| stdout.path == outfile.path and stderr.path == null_file end.returns(rval) Puppet::Util::Execution.execute('test command', {}) end end end describe "on Windows", :if => Puppet.features.microsoft_windows? do it "should always close the process and thread handles" do Puppet::Util::Execution.stubs(:execute_windows).returns(proc_info_stub) Puppet::Util::Windows::Process.expects(:wait_process).with(process_handle).raises('whatever') FFI::WIN32.expects(:CloseHandle).with(thread_handle) FFI::WIN32.expects(:CloseHandle).with(process_handle) expect { Puppet::Util::Execution.execute('test command') }.to raise_error(RuntimeError) end it "should return the correct exit status even when exit status is greater than 256" do real_exit_status = 3010 Puppet::Util::Execution.stubs(:execute_windows).returns(proc_info_stub) stub_process_wait(real_exit_status) $CHILD_STATUS.stubs(:exitstatus).returns(real_exit_status % 256) # The exitstatus is changed to be mod 256 so that ruby can fit it into 8 bits. Puppet::Util::Execution.execute('test command', :failonfail => false).exitstatus.should == real_exit_status end end end describe "#execute (posix locale)", :unless => Puppet.features.microsoft_windows? do before :each do # there is a danger here that ENV will be modified by exec_posix. Normally it would only affect the ENV # of a forked process, but, in some of the previous tests in this file we're stubbing Kernel.fork., which could # allow the method to override the "real" ENV. This shouldn't be a problem for these tests because they are # not stubbing Kernel.fork, but, better safe than sorry... so, to guard against this, we'll capture a snapshot # of ENV before each test. @saved_env = ENV.to_hash end after :each do # capture the current environment and make sure it's the same as it was before the test cur_env = ENV.to_hash # we will get some fairly useless output if we just use the raw == operator on the hashes here, so we'll # be a bit more explicit and laborious in the name of making the error more useful... @saved_env.each_pair { |key,val| cur_env[key].should == val } (cur_env.keys - @saved_env.keys).should == [] end # build up a printf-style string that contains a command to get the value of an environment variable # from the operating system. We can substitute into this with the names of the desired environment variables later. get_env_var_cmd = 'echo $%s' # a sentinel value that we can use to emulate what locale environment variables might be set to on an international # system. lang_sentinel_value = "en_US.UTF-8" # a temporary hash that contains sentinel values for each of the locale environment variables that we override in # "execute" locale_sentinel_env = {} Puppet::Util::POSIX::LOCALE_ENV_VARS.each { |var| locale_sentinel_env[var] = lang_sentinel_value } it "should override the locale environment variables when :override_locale is not set (defaults to true)" do # temporarily override the locale environment vars with a sentinel value, so that we can confirm that # execute is actually setting them. Puppet::Util.withenv(locale_sentinel_env) do Puppet::Util::POSIX::LOCALE_ENV_VARS.each do |var| # we expect that all of the POSIX vars will have been cleared except for LANG and LC_ALL expected_value = (['LANG', 'LC_ALL'].include?(var)) ? "C" : "" Puppet::Util::Execution.execute(get_env_var_cmd % var).strip.should == expected_value end end end it "should override the LANG environment variable when :override_locale is set to true" do # temporarily override the locale environment vars with a sentinel value, so that we can confirm that # execute is actually setting them. Puppet::Util.withenv(locale_sentinel_env) do Puppet::Util::POSIX::LOCALE_ENV_VARS.each do |var| # we expect that all of the POSIX vars will have been cleared except for LANG and LC_ALL expected_value = (['LANG', 'LC_ALL'].include?(var)) ? "C" : "" Puppet::Util::Execution.execute(get_env_var_cmd % var, {:override_locale => true}).strip.should == expected_value end end end it "should *not* override the LANG environment variable when :override_locale is set to false" do # temporarily override the locale environment vars with a sentinel value, so that we can confirm that # execute is not setting them. Puppet::Util.withenv(locale_sentinel_env) do Puppet::Util::POSIX::LOCALE_ENV_VARS.each do |var| Puppet::Util::Execution.execute(get_env_var_cmd % var, {:override_locale => false}).strip.should == lang_sentinel_value end end end it "should have restored the LANG and locale environment variables after execution" do # we'll do this once without any sentinel values, to give us a little more test coverage orig_env_vals = {} Puppet::Util::POSIX::LOCALE_ENV_VARS.each do |var| orig_env_vals[var] = ENV[var] end # now we can really execute any command--doesn't matter what it is... Puppet::Util::Execution.execute(get_env_var_cmd % 'anything', {:override_locale => true}) # now we check and make sure the original environment was restored Puppet::Util::POSIX::LOCALE_ENV_VARS.each do |var| ENV[var].should == orig_env_vals[var] end # now, once more... but with our sentinel values Puppet::Util.withenv(locale_sentinel_env) do # now we can really execute any command--doesn't matter what it is... Puppet::Util::Execution.execute(get_env_var_cmd % 'anything', {:override_locale => true}) # now we check and make sure the original environment was restored Puppet::Util::POSIX::LOCALE_ENV_VARS.each do |var| ENV[var].should == locale_sentinel_env[var] end end end end describe "#execute (posix user env vars)", :unless => Puppet.features.microsoft_windows? do # build up a printf-style string that contains a command to get the value of an environment variable # from the operating system. We can substitute into this with the names of the desired environment variables later. get_env_var_cmd = 'echo $%s' # a sentinel value that we can use to emulate what locale environment variables might be set to on an international # system. user_sentinel_value = "Abracadabra" # a temporary hash that contains sentinel values for each of the locale environment variables that we override in # "execute" user_sentinel_env = {} Puppet::Util::POSIX::USER_ENV_VARS.each { |var| user_sentinel_env[var] = user_sentinel_value } it "should unset user-related environment vars during execution" do # first we set up a temporary execution environment with sentinel values for the user-related environment vars # that we care about. Puppet::Util.withenv(user_sentinel_env) do # with this environment, we loop over the vars in question Puppet::Util::POSIX::USER_ENV_VARS.each do |var| # ensure that our temporary environment is set up as we expect ENV[var].should == user_sentinel_env[var] # run an "exec" via the provider and ensure that it unsets the vars Puppet::Util::Execution.execute(get_env_var_cmd % var).strip.should == "" # ensure that after the exec, our temporary env is still intact ENV[var].should == user_sentinel_env[var] end end end it "should have restored the user-related environment variables after execution" do # we'll do this once without any sentinel values, to give us a little more test coverage orig_env_vals = {} Puppet::Util::POSIX::USER_ENV_VARS.each do |var| orig_env_vals[var] = ENV[var] end # now we can really execute any command--doesn't matter what it is... Puppet::Util::Execution.execute(get_env_var_cmd % 'anything') # now we check and make sure the original environment was restored Puppet::Util::POSIX::USER_ENV_VARS.each do |var| ENV[var].should == orig_env_vals[var] end # now, once more... but with our sentinel values Puppet::Util.withenv(user_sentinel_env) do # now we can really execute any command--doesn't matter what it is... Puppet::Util::Execution.execute(get_env_var_cmd % 'anything') # now we check and make sure the original environment was restored Puppet::Util::POSIX::USER_ENV_VARS.each do |var| ENV[var].should == user_sentinel_env[var] end end end end describe "#execute (debug logging)" do before :each do stub_process_wait(0) if Puppet.features.microsoft_windows? Puppet::Util::Execution.stubs(:execute_windows).returns(proc_info_stub) else Puppet::Util::Execution.stubs(:execute_posix).returns(pid) end end it "should log if no uid or gid specified" do Puppet::Util::Execution.expects(:debug).with("Executing: 'echo hello'") Puppet::Util::Execution.execute('echo hello') end it "should log numeric uid if specified" do Puppet::Util::Execution.expects(:debug).with("Executing with uid=100: 'echo hello'") Puppet::Util::Execution.execute('echo hello', {:uid => 100}) end it "should log numeric gid if specified" do Puppet::Util::Execution.expects(:debug).with("Executing with gid=500: 'echo hello'") Puppet::Util::Execution.execute('echo hello', {:gid => 500}) end it "should log numeric uid and gid if specified" do Puppet::Util::Execution.expects(:debug).with("Executing with uid=100 gid=500: 'echo hello'") Puppet::Util::Execution.execute('echo hello', {:uid => 100, :gid => 500}) end it "should log string uid if specified" do Puppet::Util::Execution.expects(:debug).with("Executing with uid=myuser: 'echo hello'") Puppet::Util::Execution.execute('echo hello', {:uid => 'myuser'}) end it "should log string gid if specified" do Puppet::Util::Execution.expects(:debug).with("Executing with gid=mygroup: 'echo hello'") Puppet::Util::Execution.execute('echo hello', {:gid => 'mygroup'}) end it "should log string uid and gid if specified" do Puppet::Util::Execution.expects(:debug).with("Executing with uid=myuser gid=mygroup: 'echo hello'") Puppet::Util::Execution.execute('echo hello', {:uid => 'myuser', :gid => 'mygroup'}) end it "should log numeric uid and string gid if specified" do Puppet::Util::Execution.expects(:debug).with("Executing with uid=100 gid=mygroup: 'echo hello'") Puppet::Util::Execution.execute('echo hello', {:uid => 100, :gid => 'mygroup'}) end end describe "after execution" do before :each do stub_process_wait(0) if Puppet.features.microsoft_windows? Puppet::Util::Execution.stubs(:execute_windows).returns(proc_info_stub) else Puppet::Util::Execution.stubs(:execute_posix).returns(pid) end end it "should wait for the child process to exit" do Puppet::Util::Execution.stubs(:wait_for_output) Puppet::Util::Execution.execute('test command') end it "should close the stdin/stdout/stderr files used by the child" do stdin = mock 'file', :close stdout = mock 'file', :close stderr = mock 'file', :close File.expects(:open). times(3). returns(stdin). then.returns(stdout). then.returns(stderr) Puppet::Util::Execution.execute('test command', {:squelch => true, :combine => false}) end it "should read and return the output if squelch is false" do stdout = Puppet::FileSystem::Uniquefile.new('test') Puppet::FileSystem::Uniquefile.stubs(:new).returns(stdout) stdout.write("My expected command output") Puppet::Util::Execution.execute('test command').should == "My expected command output" end it "should not read the output if squelch is true" do stdout = Puppet::FileSystem::Uniquefile.new('test') Puppet::FileSystem::Uniquefile.stubs(:new).returns(stdout) stdout.write("My expected command output") Puppet::Util::Execution.execute('test command', :squelch => true).should == '' end it "should delete the file used for output if squelch is false" do stdout = Puppet::FileSystem::Uniquefile.new('test') path = stdout.path Puppet::FileSystem::Uniquefile.stubs(:new).returns(stdout) Puppet::Util::Execution.execute('test command') - Puppet::FileSystem.exist?(path).should be_false + Puppet::FileSystem.exist?(path).should be_falsey end it "should not raise an error if the file is open" do stdout = Puppet::FileSystem::Uniquefile.new('test') Puppet::FileSystem::Uniquefile.stubs(:new).returns(stdout) file = File.new(stdout.path, 'r') Puppet::Util::Execution.execute('test command') end it "should raise an error if failonfail is true and the child failed" do stub_process_wait(1) expect { subject.execute('fail command', :failonfail => true) }.to raise_error(Puppet::ExecutionFailure, /Execution of 'fail command' returned 1/) end it "should not raise an error if failonfail is false and the child failed" do stub_process_wait(1) subject.execute('fail command', :failonfail => false) end it "should not raise an error if failonfail is true and the child succeeded" do stub_process_wait(0) subject.execute('fail command', :failonfail => true) end it "should not raise an error if failonfail is false and the child succeeded" do stub_process_wait(0) subject.execute('fail command', :failonfail => false) end it "should default failonfail to true when no options are specified" do stub_process_wait(1) expect { subject.execute('fail command') }.to raise_error(Puppet::ExecutionFailure, /Execution of 'fail command' returned 1/) end it "should default failonfail to false when options are specified, but failonfail is not" do stub_process_wait(1) subject.execute('fail command', { :combine => true }) end it "should default failonfail to false when an empty hash of options is specified" do stub_process_wait(1) subject.execute('fail command', {}) end it "should raise an error if a nil option is specified" do expect { Puppet::Util::Execution.execute('fail command', nil) }.to raise_error(TypeError, /(can\'t convert|no implicit conversion of) nil into Hash/) end end end describe "#execpipe" do it "should execute a string as a string" do Puppet::Util::Execution.expects(:open).with('| echo hello 2>&1').returns('hello') Puppet::Util::Execution.expects(:exitstatus).returns(0) Puppet::Util::Execution.execpipe('echo hello').should == 'hello' end it "should print meaningful debug message for string argument" do Puppet::Util::Execution.expects(:debug).with("Executing 'echo hello'") Puppet::Util::Execution.expects(:open).with('| echo hello 2>&1').returns('hello') Puppet::Util::Execution.expects(:exitstatus).returns(0) Puppet::Util::Execution.execpipe('echo hello') end it "should print meaningful debug message for array argument" do Puppet::Util::Execution.expects(:debug).with("Executing 'echo hello'") Puppet::Util::Execution.expects(:open).with('| echo hello 2>&1').returns('hello') Puppet::Util::Execution.expects(:exitstatus).returns(0) Puppet::Util::Execution.execpipe(['echo','hello']) end it "should execute an array by pasting together with spaces" do Puppet::Util::Execution.expects(:open).with('| echo hello 2>&1').returns('hello') Puppet::Util::Execution.expects(:exitstatus).returns(0) Puppet::Util::Execution.execpipe(['echo', 'hello']).should == 'hello' end it "should fail if asked to fail, and the child does" do Puppet::Util::Execution.stubs(:open).with('| echo hello 2>&1').returns('error message') Puppet::Util::Execution.expects(:exitstatus).returns(1) expect { Puppet::Util::Execution.execpipe('echo hello') }. to raise_error Puppet::ExecutionFailure, /error message/ end it "should not fail if asked not to fail, and the child does" do Puppet::Util::Execution.stubs(:open).returns('error message') Puppet::Util::Execution.execpipe('echo hello', false).should == 'error message' end end end diff --git a/spec/unit/util/feature_spec.rb b/spec/unit/util/feature_spec.rb index e6d844533..3b0796453 100755 --- a/spec/unit/util/feature_spec.rb +++ b/spec/unit/util/feature_spec.rb @@ -1,106 +1,106 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/feature' describe Puppet::Util::Feature do before do @features = Puppet::Util::Feature.new("features") @features.stubs(:warn) end it "should consider undefined features to be absent" do @features.should_not be_defined_feature end it "should be able to add new features" do @features.add(:myfeature) {} @features.should respond_to(:myfeature?) end it "should call associated code when loading a feature" do $loaded_feature = false @features.add(:myfeature) { $loaded_feature = true} - $loaded_feature.should be_true + $loaded_feature.should be_truthy end it "should consider a feature absent when the feature load fails" do @features.add(:failer) { raise "foo" } @features.should_not be_failer end it "should consider a feature to be absent when the feature load returns false" do @features.add(:failer) { false } @features.should_not be_failer end it "should consider a feature to be present when the feature load returns true" do @features.add(:available) { true } @features.should be_available end it "should cache the results of a feature load via code block" do $loaded_feature = 0 @features.add(:myfeature) { $loaded_feature += 1 } @features.myfeature? @features.myfeature? $loaded_feature.should == 1 end it "should invalidate the cache for the feature when loading" do # block defined features are evaluated at load time @features.add(:myfeature) { false } @features.should_not be_myfeature # features with no block have deferred evaluation so an existing cached # value would take precedence @features.add(:myfeature) @features.should be_myfeature end it "should support features with libraries" do lambda { @features.add(:puppet, :libs => %w{puppet}) }.should_not raise_error end it "should consider a feature to be present if all of its libraries are present" do @features.add(:myfeature, :libs => %w{foo bar}) @features.expects(:require).with("foo") @features.expects(:require).with("bar") @features.should be_myfeature end it "should log and consider a feature to be absent if any of its libraries are absent" do @features.add(:myfeature, :libs => %w{foo bar}) @features.expects(:require).with("foo").raises(LoadError) @features.stubs(:require).with("bar") Puppet.expects(:debug) @features.should_not be_myfeature end it "should change the feature to be present when its libraries become available" do @features.add(:myfeature, :libs => %w{foo bar}) @features.expects(:require).twice().with("foo").raises(LoadError).then.returns(nil) @features.stubs(:require).with("bar") Puppet::Util::RubyGems::Source.stubs(:source).returns(Puppet::Util::RubyGems::OldGemsSource) Puppet::Util::RubyGems::OldGemsSource.any_instance.expects(:clear_paths).times(3) Puppet.expects(:debug) @features.should_not be_myfeature @features.should be_myfeature end it "should cache load failures when configured to do so" do Puppet[:always_cache_features] = true @features.add(:myfeature, :libs => %w{foo bar}) @features.expects(:require).with("foo").raises(LoadError) @features.should_not be_myfeature # second call would cause an expectation exception if 'require' was # called a second time @features.should_not be_myfeature end end diff --git a/spec/unit/util/filetype_spec.rb b/spec/unit/util/filetype_spec.rb index 304b49352..eb0108b8f 100755 --- a/spec/unit/util/filetype_spec.rb +++ b/spec/unit/util/filetype_spec.rb @@ -1,211 +1,211 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/filetype' # XXX Import all of the tests into this file. describe Puppet::Util::FileType do describe "the flat filetype" do let(:path) { '/my/file' } let(:type) { Puppet::Util::FileType.filetype(:flat) } let(:file) { type.new(path) } it "should exist" do type.should_not be_nil end describe "when the file already exists" do it "should return the file's contents when asked to read it" do Puppet::FileSystem.expects(:exist?).with(path).returns true File.expects(:read).with(path).returns "my text" file.read.should == "my text" end it "should unlink the file when asked to remove it" do Puppet::FileSystem.expects(:exist?).with(path).returns true Puppet::FileSystem.expects(:unlink).with(path) file.remove end end describe "when the file does not exist" do it "should return an empty string when asked to read the file" do Puppet::FileSystem.expects(:exist?).with(path).returns false file.read.should == "" end end describe "when writing the file" do let(:tempfile) { stub 'tempfile', :print => nil, :close => nil, :flush => nil, :path => "/other/file" } before do FileUtils.stubs(:cp) Tempfile.stubs(:new).returns tempfile end it "should first create a temp file and copy its contents over to the file location" do Tempfile.expects(:new).with("puppet").returns tempfile tempfile.expects(:print).with("my text") tempfile.expects(:flush) tempfile.expects(:close) FileUtils.expects(:cp).with(tempfile.path, path) file.write "my text" end it "should set the selinux default context on the file" do file.expects(:set_selinux_default_context).with(path) file.write "eh" end end describe "when backing up a file" do it "should do nothing if the file does not exist" do Puppet::FileSystem.expects(:exist?).with(path).returns false file.expects(:bucket).never file.backup end it "should use its filebucket to backup the file if it exists" do Puppet::FileSystem.expects(:exist?).with(path).returns true bucket = mock 'bucket' bucket.expects(:backup).with(path) file.expects(:bucket).returns bucket file.backup end it "should use the default filebucket" do bucket = mock 'bucket' bucket.expects(:bucket).returns "mybucket" Puppet::Type.type(:filebucket).expects(:mkdefaultbucket).returns bucket file.bucket.should == "mybucket" end end end shared_examples_for "crontab provider" do let(:cron) { type.new('no_such_user') } let(:crontab) { File.read(my_fixture(crontab_output)) } let(:options) { { :failonfail => true, :combine => true } } let(:uid) { 'no_such_user' } let(:user_options) { options.merge({:uid => uid}) } it "should exist" do type.should_not be_nil end # make Puppet::Util::SUIDManager return something deterministic, not the # uid of the user running the tests, except where overridden below. before :each do Puppet::Util::SUIDManager.stubs(:uid).returns 1234 end describe "#read" do it "should run crontab -l as the target user" do Puppet::Util::Execution.expects(:execute).with(['crontab', '-l'], user_options).returns crontab cron.read.should == crontab end it "should not switch user if current user is the target user" do Puppet::Util.expects(:uid).with(uid).returns 9000 Puppet::Util::SUIDManager.expects(:uid).returns 9000 Puppet::Util::Execution.expects(:execute).with(['crontab', '-l'], options).returns crontab cron.read.should == crontab end it "should treat an absent crontab as empty" do Puppet::Util::Execution.expects(:execute).with(['crontab', '-l'], user_options).raises(Puppet::ExecutionFailure, absent_crontab) cron.read.should == '' end it "should raise an error if the user is not authorized to use cron" do Puppet::Util::Execution.expects(:execute).with(['crontab', '-l'], user_options).raises(Puppet::ExecutionFailure, unauthorized_crontab) expect { cron.read }.to raise_error Puppet::Error, /User #{uid} not authorized to use cron/ end end describe "#remove" do it "should run crontab -r as the target user" do Puppet::Util::Execution.expects(:execute).with(['crontab', '-r'], user_options) cron.remove end it "should not switch user if current user is the target user" do Puppet::Util.expects(:uid).with(uid).returns 9000 Puppet::Util::SUIDManager.expects(:uid).returns 9000 Puppet::Util::Execution.expects(:execute).with(['crontab','-r'], options) cron.remove end end describe "#write" do before :each do @tmp_cron = Tempfile.new("puppet_crontab_spec") @tmp_cron_path = @tmp_cron.path Puppet::Util.stubs(:uid).with(uid).returns 9000 Tempfile.expects(:new).with("puppet_#{name}").returns @tmp_cron end after :each do - Puppet::FileSystem.exist?(@tmp_cron_path).should be_false + Puppet::FileSystem.exist?(@tmp_cron_path).should be_falsey end it "should run crontab as the target user on a temporary file" do File.expects(:chown).with(9000, nil, @tmp_cron_path) Puppet::Util::Execution.expects(:execute).with(["crontab", @tmp_cron_path], user_options) @tmp_cron.expects(:print).with("foo\n") cron.write "foo\n" end it "should not switch user if current user is the target user" do Puppet::Util::SUIDManager.expects(:uid).returns 9000 File.expects(:chown).with(9000, nil, @tmp_cron_path) Puppet::Util::Execution.expects(:execute).with(["crontab", @tmp_cron_path], options) @tmp_cron.expects(:print).with("foo\n") cron.write "foo\n" end end end describe "the suntab filetype", :unless => Puppet.features.microsoft_windows? do let(:type) { Puppet::Util::FileType.filetype(:suntab) } let(:name) { type.name } let(:crontab_output) { 'suntab_output' } # possible crontab output was taken from here: # http://docs.oracle.com/cd/E19082-01/819-2380/sysrescron-60/index.html let(:absent_crontab) do 'crontab: can\'t open your crontab file' end let(:unauthorized_crontab) do 'crontab: you are not authorized to use cron. Sorry.' end it_should_behave_like "crontab provider" end describe "the aixtab filetype", :unless => Puppet.features.microsoft_windows? do let(:type) { Puppet::Util::FileType.filetype(:aixtab) } let(:name) { type.name } let(:crontab_output) { 'aixtab_output' } let(:absent_crontab) do '0481-103 Cannot open a file in the /var/spool/cron/crontabs directory.' end let(:unauthorized_crontab) do '0481-109 You are not authorized to use the cron command.' end it_should_behave_like "crontab provider" end end diff --git a/spec/unit/util/inifile_spec.rb b/spec/unit/util/inifile_spec.rb index f81647ad7..05e0db434 100644 --- a/spec/unit/util/inifile_spec.rb +++ b/spec/unit/util/inifile_spec.rb @@ -1,492 +1,492 @@ require 'spec_helper' require 'puppet/util/inifile' describe Puppet::Util::IniConfig::Section do subject { described_class.new('testsection', '/some/imaginary/file') } describe "determining if the section is dirty" do it "is not dirty on creation" do expect(subject).to_not be_dirty end it "is dirty if a key is changed" do subject['hello'] = 'world' expect(subject).to be_dirty end it "is dirty if the section has been explicitly marked as dirty" do subject.mark_dirty expect(subject).to be_dirty end it "is dirty if the section is marked for deletion" do subject.destroy = true expect(subject).to be_dirty end it "is clean if the section has been explicitly marked as clean" do subject['hello'] = 'world' subject.mark_clean expect(subject).to_not be_dirty end end describe "reading an entry" do it "returns nil if the key is not present" do expect(subject['hello']).to be_nil end it "returns the value if the key is specified" do subject.entries << ['hello', 'world'] expect(subject['hello']).to eq 'world' end it "ignores comments when looking for a match" do subject.entries << '#this = comment' expect(subject['#this']).to be_nil end end describe "formatting the section" do it "prefixes the output with the section header" do expect(subject.format).to eq "[testsection]\n" end it "restores comments and blank lines" do subject.entries << "#comment\n" subject.entries << " " expect(subject.format).to eq( "[testsection]\n" + "#comment\n" + " " ) end it "adds all keys that have values" do subject.entries << ['somekey', 'somevalue'] expect(subject.format).to eq("[testsection]\nsomekey=somevalue\n") end it "excludes keys that have a value of nil" do subject.entries << ['empty', nil] expect(subject.format).to eq("[testsection]\n") end it "preserves the order of the section" do subject.entries << ['firstkey', 'firstval'] subject.entries << "# I am a comment, hear me roar\n" subject.entries << ['secondkey', 'secondval'] expect(subject.format).to eq( "[testsection]\n" + "firstkey=firstval\n" + "# I am a comment, hear me roar\n" + "secondkey=secondval\n" ) end it "is empty if the section is marked for deletion" do subject.entries << ['firstkey', 'firstval'] subject.destroy = true expect(subject.format).to eq('') end end end describe Puppet::Util::IniConfig::PhysicalFile do subject { described_class.new('/some/nonexistent/file') } let(:first_sect) do sect = Puppet::Util::IniConfig::Section.new('firstsection', '/some/imaginary/file') sect.entries << "# comment\n" << ['onefish', 'redfish'] << "\n" sect end let(:second_sect) do sect = Puppet::Util::IniConfig::Section.new('secondsection', '/some/imaginary/file') sect.entries << ['twofish', 'bluefish'] sect end describe "when reading a file" do it "raises an error if the file does not exist" do subject.filetype.stubs(:read) expect { subject.read }.to raise_error(%r[Cannot read nonexistent file .*/some/nonexistent/file]) end it "passes the contents of the file to #parse" do subject.filetype.stubs(:read).returns "[section]" subject.expects(:parse).with("[section]") subject.read end end describe "when parsing a file" do describe "parsing sections" do it "creates new sections the first time that the section is found" do text = "[mysect]\n" subject.parse(text) expect(subject.contents).to have(1).items sect = subject.contents[0] expect(sect.name).to eq "mysect" end it "raises an error if a section is redefined in the file" do text = "[mysect]\n[mysect]\n" expect { subject.parse(text) }.to raise_error(Puppet::Util::IniConfig::IniParseError, /Section "mysect" is already defined, cannot redefine/) end it "raises an error if a section is redefined in the file collection" do subject.file_collection = stub('file collection', :get_section => true) text = "[mysect]\n[mysect]\n" expect { subject.parse(text) }.to raise_error(Puppet::Util::IniConfig::IniParseError, /Section "mysect" is already defined, cannot redefine/) end end describe "parsing properties" do it "raises an error if the property is not within a section" do text = "key=val\n" expect { subject.parse(text) }.to raise_error(Puppet::Util::IniConfig::IniParseError, /Property with key "key" outside of a section/) end it "adds the property to the current section" do text = "[main]\nkey=val\n" subject.parse(text) expect(subject.contents).to have(1).items sect = subject.contents[0] expect(sect['key']).to eq "val" end end describe "parsing line continuations" do it "adds the continued line to the last parsed property" do text = "[main]\nkey=val\n moreval" subject.parse(text) expect(subject.contents).to have(1).items sect = subject.contents[0] expect(sect['key']).to eq "val\n moreval" end end describe "parsing comments and whitespace" do it "treats # as a comment leader" do text = "# octothorpe comment" subject.parse(text) expect(subject.contents).to eq ["# octothorpe comment"] end it "treats ; as a comment leader" do text = "; semicolon comment" subject.parse(text) expect(subject.contents).to eq ["; semicolon comment"] end it "treates 'rem' as a comment leader" do text = "rem rapid eye movement comment" subject.parse(text) expect(subject.contents).to eq ["rem rapid eye movement comment"] end it "stores comments and whitespace in a section in the correct section" do text = "[main]\n; main section comment" subject.parse(text) sect = subject.get_section("main") expect(sect.entries).to eq ["; main section comment"] end end end it "can return all sections" do text = "[first]\n" + "; comment\n" + "[second]\n" + "key=value" subject.parse(text) sections = subject.sections expect(sections).to have(2).items expect(sections[0].name).to eq "first" expect(sections[1].name).to eq "second" end it "can retrieve a specific section" do text = "[first]\n" + "; comment\n" + "[second]\n" + "key=value" subject.parse(text) section = subject.get_section("second") expect(section.name).to eq "second" expect(section["key"]).to eq "value" end describe "formatting" do it "concatenates each formatted section in order" do subject.contents << first_sect << second_sect expected = "[firstsection]\n" + "# comment\n" + "onefish=redfish\n" + "\n" + "[secondsection]\n" + "twofish=bluefish\n" expect(subject.format).to eq expected end it "includes comments that are not within a section" do subject.contents << "# This comment is not in a section\n" << first_sect << second_sect expected = "# This comment is not in a section\n" + "[firstsection]\n" + "# comment\n" + "onefish=redfish\n" + "\n" + "[secondsection]\n" + "twofish=bluefish\n" expect(subject.format).to eq expected end it "excludes sections that are marked to be destroyed" do subject.contents << first_sect << second_sect first_sect.destroy = true expected = "[secondsection]\n" + "twofish=bluefish\n" expect(subject.format).to eq expected end end describe "storing the file" do describe "with empty contents" do describe "and destroy_empty is true" do before { subject.destroy_empty = true } it "removes the file if there are no sections" do File.expects(:unlink) subject.store end it "removes the file if all sections are marked to be destroyed" do subject.contents << first_sect << second_sect first_sect.destroy = true second_sect.destroy = true File.expects(:unlink) subject.store end it "doesn't remove the file if not all sections are marked to be destroyed" do subject.contents << first_sect << second_sect first_sect.destroy = true second_sect.destroy = false File.expects(:unlink).never subject.filetype.stubs(:write) subject.store end end it "rewrites the file if destroy_empty is false" do subject.contents << first_sect << second_sect first_sect.destroy = true second_sect.destroy = true File.expects(:unlink).never subject.stubs(:format).returns "formatted" subject.filetype.expects(:write).with("formatted") subject.store end end it "rewrites the file if any section is dirty" do subject.contents << first_sect << second_sect first_sect.mark_dirty second_sect.mark_clean subject.stubs(:format).returns "formatted" subject.filetype.expects(:write).with("formatted") subject.store end it "doesn't modify the file if all sections are clean" do subject.contents << first_sect << second_sect first_sect.mark_clean second_sect.mark_clean subject.stubs(:format).returns "formatted" subject.filetype.expects(:write).never subject.store end end end describe Puppet::Util::IniConfig::FileCollection do let(:path_a) { '/some/nonexistent/file/a' } let(:path_b) { '/some/nonexistent/file/b' } let(:file_a) { Puppet::Util::IniConfig::PhysicalFile.new(path_a) } let(:file_b) { Puppet::Util::IniConfig::PhysicalFile.new(path_b) } let(:sect_a1) { Puppet::Util::IniConfig::Section.new('sect_a1', path_a) } let(:sect_a2) { Puppet::Util::IniConfig::Section.new('sect_a2', path_a) } let(:sect_b1) { Puppet::Util::IniConfig::Section.new('sect_b1', path_b) } let(:sect_b2) { Puppet::Util::IniConfig::Section.new('sect_b2', path_b) } before do file_a.contents << sect_a1 << sect_a2 file_b.contents << sect_b1 << sect_b2 end describe "reading a file" do let(:stub_file) { stub('Physical file') } it "creates a new PhysicalFile and uses that to read the file" do stub_file.expects(:read) stub_file.expects(:file_collection=) Puppet::Util::IniConfig::PhysicalFile.expects(:new).with(path_a).returns stub_file subject.read(path_a) end it "stores the PhysicalFile and the path to the file" do stub_file.stubs(:read) stub_file.stubs(:file_collection=) Puppet::Util::IniConfig::PhysicalFile.stubs(:new).with(path_a).returns stub_file subject.read(path_a) path, physical_file = subject.files.first expect(path).to eq(path_a) expect(physical_file).to eq stub_file end end describe "storing all files" do before do subject.files[path_a] = file_a subject.files[path_b] = file_b end it "stores all files in the collection" do file_a.expects(:store).once file_b.expects(:store).once subject.store end end describe "iterating over sections" do before do subject.files[path_a] = file_a subject.files[path_b] = file_b end it "yields every section from every file" do [sect_a1, sect_a2, sect_b1, sect_b2].each do |sect| sect.expects(:touch).once end subject.each_section do |sect| sect.touch end end end describe "iterating over files" do before do subject.files[path_a] = file_a subject.files[path_b] = file_b end it "yields the path to every file in the collection" do seen = [] subject.each_file do |file| seen << file end expect(seen).to include(path_a) expect(seen).to include(path_b) end end describe "retrieving a specific section" do before do subject.files[path_a] = file_a subject.files[path_b] = file_b end it "retrieves the first section defined" do expect(subject.get_section('sect_b1')).to eq sect_b1 end it "returns nil if there was no section with the given name" do expect(subject.get_section('nope')).to be_nil end it "allows #[] to be used as an alias to #get_section" do expect(subject['b2']).to eq subject.get_section('b2') end end describe "checking if a section has been defined" do before do subject.files[path_a] = file_a subject.files[path_b] = file_b end it "is true if a section with the given name is defined" do - expect(subject.include?('sect_a1')).to be_true + expect(subject.include?('sect_a1')).to be_truthy end it "is false if a section with the given name can't be found" do - expect(subject.include?('nonexistent')).to be_false + expect(subject.include?('nonexistent')).to be_falsey end end describe "adding a new section" do before do subject.files[path_a] = file_a subject.files[path_b] = file_b end it "adds the section to the appropriate file" do file_a.expects(:add_section).with('newsect') subject.add_section('newsect', path_a) end end end diff --git a/spec/unit/util/lockfile_spec.rb b/spec/unit/util/lockfile_spec.rb index 33755dad2..8fbfa8bdf 100644 --- a/spec/unit/util/lockfile_spec.rb +++ b/spec/unit/util/lockfile_spec.rb @@ -1,118 +1,118 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/lockfile' module LockfileSpecHelper def self.run_in_forks(count, &blk) forks = {} results = [] count.times do |i| forks[i] = {} forks[i][:read], forks[i][:write] = IO.pipe forks[i][:pid] = fork do forks[i][:read].close res = yield Marshal.dump(res, forks[i][:write]) exit! end end count.times do |i| forks[i][:write].close result = forks[i][:read].read forks[i][:read].close Process.wait2(forks[i][:pid]) results << Marshal.load(result) end results end end describe Puppet::Util::Lockfile do require 'puppet_spec/files' include PuppetSpec::Files before(:each) do @lockfile = tmpfile("lock") @lock = Puppet::Util::Lockfile.new(@lockfile) end describe "#lock" do it "should return true if it successfully locked" do - @lock.lock.should be_true + @lock.lock.should be_truthy end it "should return false if already locked" do @lock.lock - @lock.lock.should be_false + @lock.lock.should be_falsey end it "should create a lock file" do @lock.lock - Puppet::FileSystem.exist?(@lockfile).should be_true + Puppet::FileSystem.exist?(@lockfile).should be_truthy end # We test simultaneous locks using fork which isn't supported on Windows. it "should not be acquired by another process", :unless => Puppet.features.microsoft_windows? do 30.times do forks = 3 results = LockfileSpecHelper.run_in_forks(forks) do @lock.lock(Process.pid) end @lock.unlock # Confirm one fork returned true and everyone else false. (results - [true]).size.should == forks - 1 (results - [false]).size.should == 1 end end it "should create a lock file containing a string" do data = "foofoo barbar" @lock.lock(data) File.read(@lockfile).should == data end end describe "#unlock" do it "should return true when unlocking" do @lock.lock - @lock.unlock.should be_true + @lock.unlock.should be_truthy end it "should return false when not locked" do - @lock.unlock.should be_false + @lock.unlock.should be_falsey end it "should clear the lock file" do File.open(@lockfile, 'w') { |fd| fd.print("locked") } @lock.unlock - Puppet::FileSystem.exist?(@lockfile).should be_false + Puppet::FileSystem.exist?(@lockfile).should be_falsey end end it "should be locked when locked" do @lock.lock @lock.should be_locked end it "should not be locked when not locked" do @lock.should_not be_locked end it "should not be locked when unlocked" do @lock.lock @lock.unlock @lock.should_not be_locked end it "should return the lock data" do data = "foofoo barbar" @lock.lock(data) @lock.lock_data.should == data end end diff --git a/spec/unit/util/log/destinations_spec.rb b/spec/unit/util/log/destinations_spec.rb index e48972a62..b3a049ac6 100755 --- a/spec/unit/util/log/destinations_spec.rb +++ b/spec/unit/util/log/destinations_spec.rb @@ -1,236 +1,236 @@ #! /usr/bin/env ruby require 'spec_helper' require 'json' require 'puppet/util/log' describe Puppet::Util::Log.desttypes[:report] do before do @dest = Puppet::Util::Log.desttypes[:report] end it "should require a report at initialization" do @dest.new("foo").report.should == "foo" end it "should send new messages to the report" do report = mock 'report' dest = @dest.new(report) report.expects(:<<).with("my log") dest.handle "my log" end end describe Puppet::Util::Log.desttypes[:file] do include PuppetSpec::Files before do File.stubs(:open) # prevent actually creating the file File.stubs(:chown) # prevent chown on non existing file from failing @class = Puppet::Util::Log.desttypes[:file] end it "should default to autoflush false" do @class.new(tmpfile('log')).autoflush.should == true end describe "when matching" do shared_examples_for "file destination" do it "should match an absolute path" do - @class.match?(abspath).should be_true + @class.match?(abspath).should be_truthy end it "should not match a relative path" do - @class.match?(relpath).should be_false + @class.match?(relpath).should be_falsey end end describe "on POSIX systems", :if => Puppet.features.posix? do let (:abspath) { '/tmp/log' } let (:relpath) { 'log' } it_behaves_like "file destination" it "logs an error if it can't chown the file owner & group" do FileUtils.expects(:chown).with(Puppet[:user], Puppet[:group], abspath).raises(Errno::EPERM) Puppet.features.expects(:root?).returns(true) Puppet.expects(:err).with("Unable to set ownership to #{Puppet[:user]}:#{Puppet[:group]} for log file: #{abspath}") @class.new(abspath) end it "doesn't attempt to chown when running as non-root" do FileUtils.expects(:chown).with(Puppet[:user], Puppet[:group], abspath).never Puppet.features.expects(:root?).returns(false) @class.new(abspath) end end describe "on Windows systems", :if => Puppet.features.microsoft_windows? do let (:abspath) { 'C:\\temp\\log.txt' } let (:relpath) { 'log.txt' } it_behaves_like "file destination" end end end describe Puppet::Util::Log.desttypes[:syslog] do let (:klass) { Puppet::Util::Log.desttypes[:syslog] } # these tests can only be run when syslog is present, because # we can't stub the top-level Syslog module describe "when syslog is available", :if => Puppet.features.syslog? do before :each do Syslog.stubs(:opened?).returns(false) Syslog.stubs(:const_get).returns("LOG_KERN").returns(0) Syslog.stubs(:open) end it "should open syslog" do Syslog.expects(:open) klass.new end it "should close syslog" do Syslog.expects(:close) dest = klass.new dest.close end it "should send messages to syslog" do syslog = mock 'syslog' syslog.expects(:info).with("don't panic") Syslog.stubs(:open).returns(syslog) msg = Puppet::Util::Log.new(:level => :info, :message => "don't panic") dest = klass.new dest.handle(msg) end end describe "when syslog is unavailable" do it "should not be a suitable log destination" do Puppet.features.stubs(:syslog?).returns(false) - klass.suitable?(:syslog).should be_false + klass.suitable?(:syslog).should be_falsey end end end describe Puppet::Util::Log.desttypes[:logstash_event] do describe "when using structured log format with logstash_event schema" do before :each do @msg = Puppet::Util::Log.new(:level => :info, :message => "So long, and thanks for all the fish.", :source => "a dolphin") end it "format should fix the hash to have the correct structure" do dest = described_class.new result = dest.format(@msg) result["version"].should == 1 result["level"].should == :info result["message"].should == "So long, and thanks for all the fish." result["source"].should == "a dolphin" # timestamp should be within 10 seconds Time.parse(result["@timestamp"]).should >= ( Time.now - 10 ) end it "format returns a structure that can be converted to json" do dest = described_class.new hash = dest.format(@msg) JSON.parse(hash.to_json) end it "handle should send the output to stdout" do $stdout.expects(:puts).once dest = described_class.new dest.handle(@msg) end end end describe Puppet::Util::Log.desttypes[:console] do let (:klass) { Puppet::Util::Log.desttypes[:console] } describe "when color is available" do before :each do subject.stubs(:console_has_color?).returns(true) end it "should support color output" do Puppet[:color] = true subject.colorize(:red, 'version').should == "\e[0;31mversion\e[0m" end it "should withhold color output when not appropriate" do Puppet[:color] = false subject.colorize(:red, 'version').should == "version" end it "should handle multiple overlapping colors in a stack-like way" do Puppet[:color] = true vstring = subject.colorize(:red, 'version') subject.colorize(:green, "(#{vstring})").should == "\e[0;32m(\e[0;31mversion\e[0;32m)\e[0m" end it "should handle resets in a stack-like way" do Puppet[:color] = true vstring = subject.colorize(:reset, 'version') subject.colorize(:green, "(#{vstring})").should == "\e[0;32m(\e[mversion\e[0;32m)\e[0m" end it "should include the log message's source/context in the output when available" do Puppet[:color] = false $stdout.expects(:puts).with("Info: a hitchhiker: don't panic") msg = Puppet::Util::Log.new(:level => :info, :message => "don't panic", :source => "a hitchhiker") dest = klass.new dest.handle(msg) end end end describe ":eventlog", :if => Puppet::Util::Platform.windows? do let(:klass) { Puppet::Util::Log.desttypes[:eventlog] } def expects_message_with_type(klass, level, eventlog_type, eventlog_id) eventlog = stub('eventlog') eventlog.expects(:report_event).with(has_entries(:source => "Puppet", :event_type => eventlog_type, :event_id => eventlog_id, :data => "a hitchhiker: don't panic")) Win32::EventLog.stubs(:open).returns(eventlog) msg = Puppet::Util::Log.new(:level => level, :message => "don't panic", :source => "a hitchhiker") dest = klass.new dest.handle(msg) end it "supports the eventlog feature" do - expect(Puppet.features.eventlog?).to be_true + expect(Puppet.features.eventlog?).to be_truthy end it "logs to the Application event log" do eventlog = stub('eventlog') Win32::EventLog.expects(:open).with('Application').returns(stub('eventlog')) klass.new end it "logs :debug level as an information type event" do expects_message_with_type(klass, :debug, klass::EVENTLOG_INFORMATION_TYPE, 0x1) end it "logs :warning level as an warning type event" do expects_message_with_type(klass, :warning, klass::EVENTLOG_WARNING_TYPE, 0x2) end it "logs :err level as an error type event" do expects_message_with_type(klass, :err, klass::EVENTLOG_ERROR_TYPE, 0x3) end end diff --git a/spec/unit/util/logging_spec.rb b/spec/unit/util/logging_spec.rb index fb2bca71b..6808211aa 100755 --- a/spec/unit/util/logging_spec.rb +++ b/spec/unit/util/logging_spec.rb @@ -1,322 +1,322 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/logging' class LoggingTester include Puppet::Util::Logging end describe Puppet::Util::Logging do before do @logger = LoggingTester.new end Puppet::Util::Log.eachlevel do |level| it "should have a method for sending '#{level}' logs" do @logger.should respond_to(level) end end it "should have a method for sending a log with a specified log level" do @logger.expects(:to_s).returns "I'm a string!" Puppet::Util::Log.expects(:create).with { |args| args[:source] == "I'm a string!" and args[:level] == "loglevel" and args[:message] == "mymessage" } @logger.send_log "loglevel", "mymessage" end describe "when sending a log" do it "should use the Log's 'create' entrance method" do Puppet::Util::Log.expects(:create) @logger.notice "foo" end it "should send itself converted to a string as the log source" do @logger.expects(:to_s).returns "I'm a string!" Puppet::Util::Log.expects(:create).with { |args| args[:source] == "I'm a string!" } @logger.notice "foo" end it "should queue logs sent without a specified destination" do Puppet::Util::Log.close_all Puppet::Util::Log.expects(:queuemessage) @logger.notice "foo" end it "should use the path of any provided resource type" do resource = Puppet::Type.type(:host).new :name => "foo" resource.expects(:path).returns "/path/to/host".to_sym Puppet::Util::Log.expects(:create).with { |args| args[:source] == "/path/to/host" } resource.notice "foo" end it "should use the path of any provided resource parameter" do resource = Puppet::Type.type(:host).new :name => "foo" param = resource.parameter(:name) param.expects(:path).returns "/path/to/param".to_sym Puppet::Util::Log.expects(:create).with { |args| args[:source] == "/path/to/param" } param.notice "foo" end it "should send the provided argument as the log message" do Puppet::Util::Log.expects(:create).with { |args| args[:message] == "foo" } @logger.notice "foo" end it "should join any provided arguments into a single string for the message" do Puppet::Util::Log.expects(:create).with { |args| args[:message] == "foo bar baz" } @logger.notice ["foo", "bar", "baz"] end [:file, :line, :tags].each do |attr| it "should include #{attr} if available" do @logger.singleton_class.send(:attr_accessor, attr) @logger.send(attr.to_s + "=", "myval") Puppet::Util::Log.expects(:create).with { |args| args[attr] == "myval" } @logger.notice "foo" end end end describe "when sending a deprecation warning" do it "does not log a message when deprecation warnings are disabled" do Puppet.expects(:[]).with(:disable_warnings).returns %w[deprecations] @logger.expects(:warning).never @logger.deprecation_warning 'foo' end it "logs the message with warn" do @logger.expects(:warning).with do |msg| msg =~ /^foo\n/ end @logger.deprecation_warning 'foo' end it "only logs each offending line once" do @logger.expects(:warning).with do |msg| msg =~ /^foo\n/ end .once 5.times { @logger.deprecation_warning 'foo' } end it "ensures that deprecations from same origin are logged if their keys differ" do @logger.expects(:warning).with(regexp_matches(/deprecated foo/)).times(5) 5.times { |i| @logger.deprecation_warning('deprecated foo', :key => "foo#{i}") } end it "does not duplicate deprecations for a given key" do @logger.expects(:warning).with(regexp_matches(/deprecated foo/)).once 5.times { @logger.deprecation_warning('deprecated foo', :key => 'foo-msg') } end it "only logs the first 100 messages" do (1..100).each { |i| @logger.expects(:warning).with do |msg| msg =~ /^#{i}\n/ end .once # since the deprecation warning will only log each offending line once, we have to do some tomfoolery # here in order to make it think each of these calls is coming from a unique call stack; we're basically # mocking the method that it would normally use to find the call stack. @logger.expects(:get_deprecation_offender).returns(["deprecation log count test ##{i}"]) @logger.deprecation_warning i } @logger.expects(:warning).with(101).never @logger.deprecation_warning 101 end end describe "when sending a puppet_deprecation_warning" do it "requires file and line or key options" do expect do @logger.puppet_deprecation_warning("foo") end.to raise_error(Puppet::DevError, /Need either :file and :line, or :key/) expect do @logger.puppet_deprecation_warning("foo", :file => 'bar') end.to raise_error(Puppet::DevError, /Need either :file and :line, or :key/) expect do @logger.puppet_deprecation_warning("foo", :key => 'akey') @logger.puppet_deprecation_warning("foo", :file => 'afile', :line => 1) end.to_not raise_error end it "warns with file and line" do @logger.expects(:warning).with(regexp_matches(/deprecated foo.*afile:5/m)) @logger.puppet_deprecation_warning("deprecated foo", :file => 'afile', :line => 5) end it "warns keyed from file and line" do @logger.expects(:warning).with(regexp_matches(/deprecated foo.*afile:5/m)).once 5.times do @logger.puppet_deprecation_warning("deprecated foo", :file => 'afile', :line => 5) end end it "warns with separate key only once regardless of file and line" do @logger.expects(:warning).with(regexp_matches(/deprecated foo.*afile:5/m)).once @logger.puppet_deprecation_warning("deprecated foo", :key => 'some_key', :file => 'afile', :line => 5) @logger.puppet_deprecation_warning("deprecated foo", :key => 'some_key', :file => 'bfile', :line => 3) end it "warns with key but no file and line" do @logger.expects(:warning).with(regexp_matches(/deprecated foo.*unknown:unknown/m)) @logger.puppet_deprecation_warning("deprecated foo", :key => 'some_key') end end describe "when formatting exceptions" do it "should be able to format a chain of exceptions" do exc3 = Puppet::Error.new("original") exc3.set_backtrace(["1.rb:4:in `a'","2.rb:2:in `b'","3.rb:1"]) exc2 = Puppet::Error.new("second", exc3) exc2.set_backtrace(["4.rb:8:in `c'","5.rb:1:in `d'","6.rb:3"]) exc1 = Puppet::Error.new("third", exc2) exc1.set_backtrace(["7.rb:31:in `e'","8.rb:22:in `f'","9.rb:9"]) # whoa ugly @logger.format_exception(exc1).should =~ /third .*7\.rb:31:in `e' .*8\.rb:22:in `f' .*9\.rb:9 Wrapped exception: second .*4\.rb:8:in `c' .*5\.rb:1:in `d' .*6\.rb:3 Wrapped exception: original .*1\.rb:4:in `a' .*2\.rb:2:in `b' .*3\.rb:1/ end end describe 'when Facter' do after :each do # Unstub these calls as there is global code run after # each spec that may reset the log level to debug Facter.unstub(:respond_to?) Facter.unstub(:debugging) end describe 'does not support debugging' do before :each do Facter.stubs(:respond_to?).with(:debugging).returns false Facter.stubs(:debugging).never end it 'does not enable Facter debugging for debug level' do Puppet::Util::Log.level = :debug end it 'does not enable Facter debugging non-debug level' do Puppet::Util::Log.level = :info end end describe 'does support debugging' do before :each do Facter.stubs(:respond_to?).with(:debugging).returns true end it 'enables Facter debugging when debug level' do Facter.stubs(:debugging).with(true) Puppet::Util::Log.level = :debug end it 'disables Facter debugging when not debug level' do Facter.stubs(:debugging).with(false) Puppet::Util::Log.level = :info end end describe 'does not support trace' do before :each do Facter.stubs(:respond_to?).with(:trace).returns false Facter.stubs(:trace).never end it 'does not enable Facter trace when enabled' do Puppet[:trace] = true end it 'does not enable Facter trace when disabled' do Puppet[:trace] = false end end describe 'does support trace' do before :each do Facter.stubs(:respond_to?).with(:trace).returns true end it 'enables Facter trace when enabled' do Facter.stubs(:trace).with(true) Puppet[:trace] = true end it 'disables Facter trace when disabled' do Facter.stubs(:trace).with(false) Puppet[:trace] = false end end describe 'does not support on_message' do before :each do Facter.stubs(:respond_to?).with(:on_message).returns false Facter.stubs(:on_message).never end it 'does not call Facter.on_message' do - Puppet::Util::Logging::setup_facter_logging!.should be_false + Puppet::Util::Logging::setup_facter_logging!.should be_falsey end end describe 'does support on_message' do before :each do Facter.stubs(:respond_to?).with(:on_message).returns true end def setup(level, message) Facter.stubs(:on_message).yields level, message # Transform from Facter level to Puppet level case level when :trace level = :debug when :warn level = :warning when :error level = :err when :fatal level = :crit end Puppet::Util::Log.stubs(:create).with do |options| options[:level].should eq(level) options[:message].should eq(message) options[:source].should eq('Facter') end.once end [:trace, :debug, :info, :warn, :error, :fatal].each do |level| it "calls Facter.on_message and handles #{level} messages" do setup(level, "#{level} message") - Puppet::Util::Logging::setup_facter_logging!.should be_true + Puppet::Util::Logging::setup_facter_logging!.should be_truthy end end end end end diff --git a/spec/unit/util/pidlock_spec.rb b/spec/unit/util/pidlock_spec.rb index fcef7aa31..4a5e1702b 100644 --- a/spec/unit/util/pidlock_spec.rb +++ b/spec/unit/util/pidlock_spec.rb @@ -1,218 +1,218 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/pidlock' describe Puppet::Util::Pidlock do require 'puppet_spec/files' include PuppetSpec::Files before(:each) do @lockfile = tmpfile("lock") @lock = Puppet::Util::Pidlock.new(@lockfile) end describe "#lock" do it "should not be locked at start" do @lock.should_not be_locked end it "should not be mine at start" do @lock.should_not be_mine end it "should become locked" do @lock.lock @lock.should be_locked end it "should become mine" do @lock.lock @lock.should be_mine end it "should be possible to lock multiple times" do @lock.lock lambda { @lock.lock }.should_not raise_error end it "should return true when locking" do - @lock.lock.should be_true + @lock.lock.should be_truthy end it "should return true if locked by me" do @lock.lock - @lock.lock.should be_true + @lock.lock.should be_truthy end it "should create a lock file" do @lock.lock - Puppet::FileSystem.exist?(@lockfile).should be_true + Puppet::FileSystem.exist?(@lockfile).should be_truthy end it 'should create an empty lock file even when pid is missing' do Process.stubs(:pid).returns('') @lock.lock - Puppet::FileSystem.exist?(@lock.file_path).should be_true + Puppet::FileSystem.exist?(@lock.file_path).should be_truthy Puppet::FileSystem.read(@lock.file_path).should be_empty end it 'should replace an existing empty lockfile with a pid, given a subsequent lock call made against a valid pid' do # empty pid results in empty lockfile Process.stubs(:pid).returns('') @lock.lock - Puppet::FileSystem.exist?(@lock.file_path).should be_true + Puppet::FileSystem.exist?(@lock.file_path).should be_truthy # next lock call with valid pid kills existing empty lockfile Process.stubs(:pid).returns(1234) @lock.lock - Puppet::FileSystem.exist?(@lock.file_path).should be_true + Puppet::FileSystem.exist?(@lock.file_path).should be_truthy Puppet::FileSystem.read(@lock.file_path).should == '1234' end it "should expose the lock file_path" do @lock.file_path.should == @lockfile end end describe "#unlock" do it "should not be locked anymore" do @lock.lock @lock.unlock @lock.should_not be_locked end it "should return false if not locked" do - @lock.unlock.should be_false + @lock.unlock.should be_falsey end it "should return true if properly unlocked" do @lock.lock - @lock.unlock.should be_true + @lock.unlock.should be_truthy end it "should get rid of the lock file" do @lock.lock @lock.unlock - Puppet::FileSystem.exist?(@lockfile).should be_false + Puppet::FileSystem.exist?(@lockfile).should be_falsey end end describe "#locked?" do it "should return true if locked" do @lock.lock @lock.should be_locked end it "should remove the lockfile when pid is missing" do Process.stubs(:pid).returns('') @lock.lock - @lock.locked?.should be_false - Puppet::FileSystem.exist?(@lock.file_path).should be_false + @lock.locked?.should be_falsey + Puppet::FileSystem.exist?(@lock.file_path).should be_falsey end end describe '#lock_pid' do it 'should return nil if the pid is empty' do # fake pid to get empty lockfile Process.stubs(:pid).returns('') @lock.lock @lock.lock_pid.should == nil end end describe "with a stale lock" do before(:each) do # fake our pid to be 1234 Process.stubs(:pid).returns(1234) # lock the file @lock.lock # fake our pid to be a different pid, to simulate someone else # holding the lock Process.stubs(:pid).returns(6789) Process.stubs(:kill).with(0, 6789) Process.stubs(:kill).with(0, 1234).raises(Errno::ESRCH) end it "should not be locked" do @lock.should_not be_locked end describe "#lock" do it "should clear stale locks" do - @lock.locked?.should be_false - Puppet::FileSystem.exist?(@lockfile).should be_false + @lock.locked?.should be_falsey + Puppet::FileSystem.exist?(@lockfile).should be_falsey end it "should replace with new locks" do @lock.lock - Puppet::FileSystem.exist?(@lockfile).should be_true + Puppet::FileSystem.exist?(@lockfile).should be_truthy @lock.lock_pid.should == 6789 @lock.should be_mine @lock.should be_locked end end describe "#unlock" do it "should not be allowed" do - @lock.unlock.should be_false + @lock.unlock.should be_falsey end it "should not remove the lock file" do @lock.unlock - Puppet::FileSystem.exist?(@lockfile).should be_true + Puppet::FileSystem.exist?(@lockfile).should be_truthy end end end describe "with another process lock" do before(:each) do # fake our pid to be 1234 Process.stubs(:pid).returns(1234) # lock the file @lock.lock # fake our pid to be a different pid, to simulate someone else # holding the lock Process.stubs(:pid).returns(6789) Process.stubs(:kill).with(0, 6789) Process.stubs(:kill).with(0, 1234) end it "should be locked" do @lock.should be_locked end it "should not be mine" do @lock.should_not be_mine end describe "#lock" do it "should not be possible" do - @lock.lock.should be_false + @lock.lock.should be_falsey end it "should not overwrite the lock" do @lock.lock @lock.should_not be_mine end end describe "#unlock" do it "should not be possible" do - @lock.unlock.should be_false + @lock.unlock.should be_falsey end it "should not remove the lock file" do @lock.unlock - Puppet::FileSystem.exist?(@lockfile).should be_true + Puppet::FileSystem.exist?(@lockfile).should be_truthy end it "should still not be our lock" do @lock.unlock @lock.should_not be_mine end end end end diff --git a/spec/unit/util/selinux_spec.rb b/spec/unit/util/selinux_spec.rb index 28db297bb..f090f6ba6 100755 --- a/spec/unit/util/selinux_spec.rb +++ b/spec/unit/util/selinux_spec.rb @@ -1,311 +1,311 @@ #! /usr/bin/env ruby require 'spec_helper' require 'pathname' require 'puppet/util/selinux' include Puppet::Util::SELinux unless defined?(Selinux) module Selinux def self.is_selinux_enabled false end end end describe Puppet::Util::SELinux do describe "selinux_support?" do before do end it "should return :true if this system has SELinux enabled" do Selinux.expects(:is_selinux_enabled).returns 1 - selinux_support?.should be_true + selinux_support?.should be_truthy end it "should return :false if this system lacks SELinux" do Selinux.expects(:is_selinux_enabled).returns 0 - selinux_support?.should be_false + selinux_support?.should be_falsey end it "should return nil if /proc/mounts does not exist" do File.stubs(:open).with("/proc/mounts").raises("No such file or directory - /proc/mounts") read_mounts.should == nil end end describe "read_mounts" do before :each do fh = stub 'fh', :close => nil File.stubs(:open).with("/proc/mounts").returns fh fh.expects(:read_nonblock).times(2).returns("rootfs / rootfs rw 0 0\n/dev/root / ext3 rw,relatime,errors=continue,user_xattr,acl,data=ordered 0 0\n/dev /dev tmpfs rw,relatime,mode=755 0 0\n/proc /proc proc rw,relatime 0 0\n/sys /sys sysfs rw,relatime 0 0\n192.168.1.1:/var/export /mnt/nfs nfs rw,relatime,vers=3,rsize=32768,wsize=32768,namlen=255,hard,nointr,proto=tcp,timeo=600,retrans=2,sec=sys,mountaddr=192.168.1.1,mountvers=3,mountproto=udp,addr=192.168.1.1 0 0\n").then.raises EOFError end it "should parse the contents of /proc/mounts" do read_mounts.should == { '/' => 'ext3', '/sys' => 'sysfs', '/mnt/nfs' => 'nfs', '/proc' => 'proc', '/dev' => 'tmpfs' } end end describe "filesystem detection" do before :each do self.stubs(:read_mounts).returns({ '/' => 'ext3', '/sys' => 'sysfs', '/mnt/nfs' => 'nfs', '/proc' => 'proc', '/dev' => 'tmpfs' }) end it "should match a path on / to ext3" do find_fs('/etc/puppet/testfile').should == "ext3" end it "should match a path on /mnt/nfs to nfs" do find_fs('/mnt/nfs/testfile/foobar').should == "nfs" end it "should return true for a capable filesystem" do - selinux_label_support?('/etc/puppet/testfile').should be_true + selinux_label_support?('/etc/puppet/testfile').should be_truthy end it "should return false for a noncapable filesystem" do - selinux_label_support?('/mnt/nfs/testfile').should be_false + selinux_label_support?('/mnt/nfs/testfile').should be_falsey end it "(#8714) don't follow symlinks when determining file systems", :unless => Puppet.features.microsoft_windows? do scratch = Pathname(PuppetSpec::Files.tmpdir('selinux')) self.stubs(:read_mounts).returns({ '/' => 'ext3', scratch + 'nfs' => 'nfs', }) (scratch + 'foo').make_symlink('nfs/bar') - selinux_label_support?(scratch + 'foo').should be_true + selinux_label_support?(scratch + 'foo').should be_truthy end it "should handle files that don't exist" do scratch = Pathname(PuppetSpec::Files.tmpdir('selinux')) - selinux_label_support?(scratch + 'nonesuch').should be_true + selinux_label_support?(scratch + 'nonesuch').should be_truthy end end describe "get_selinux_current_context" do it "should return nil if no SELinux support" do self.expects(:selinux_support?).returns false get_selinux_current_context("/foo").should be_nil end it "should return a context" do self.expects(:selinux_support?).returns true Selinux.expects(:lgetfilecon).with("/foo").returns [0, "user_u:role_r:type_t:s0"] get_selinux_current_context("/foo").should == "user_u:role_r:type_t:s0" end it "should return nil if lgetfilecon fails" do self.expects(:selinux_support?).returns true Selinux.expects(:lgetfilecon).with("/foo").returns -1 get_selinux_current_context("/foo").should be_nil end end describe "get_selinux_default_context" do it "should return nil if no SELinux support" do self.expects(:selinux_support?).returns false get_selinux_default_context("/foo").should be_nil end it "should return a context if a default context exists" do self.expects(:selinux_support?).returns true fstat = stub 'File::Stat', :mode => 0 Puppet::FileSystem.expects(:lstat).with('/foo').returns(fstat) self.expects(:find_fs).with("/foo").returns "ext3" Selinux.expects(:matchpathcon).with("/foo", 0).returns [0, "user_u:role_r:type_t:s0"] get_selinux_default_context("/foo").should == "user_u:role_r:type_t:s0" end it "handles permission denied errors by issuing a warning" do self.stubs(:selinux_support?).returns true self.stubs(:selinux_label_support?).returns true Selinux.stubs(:matchpathcon).with("/root/chuj", 0).returns(-1) self.stubs(:file_lstat).with("/root/chuj").raises(Errno::EACCES, "/root/chuj") get_selinux_default_context("/root/chuj").should be_nil end it "handles no such file or directory errors by issuing a warning" do self.stubs(:selinux_support?).returns true self.stubs(:selinux_label_support?).returns true Selinux.stubs(:matchpathcon).with("/root/chuj", 0).returns(-1) self.stubs(:file_lstat).with("/root/chuj").raises(Errno::ENOENT, "/root/chuj") get_selinux_default_context("/root/chuj").should be_nil end it "should return nil if matchpathcon returns failure" do self.expects(:selinux_support?).returns true fstat = stub 'File::Stat', :mode => 0 Puppet::FileSystem.expects(:lstat).with('/foo').returns(fstat) self.expects(:find_fs).with("/foo").returns "ext3" Selinux.expects(:matchpathcon).with("/foo", 0).returns -1 get_selinux_default_context("/foo").should be_nil end it "should return nil if selinux_label_support returns false" do self.expects(:selinux_support?).returns true self.expects(:find_fs).with("/foo").returns "nfs" get_selinux_default_context("/foo").should be_nil end end describe "parse_selinux_context" do it "should return nil if no context is passed" do parse_selinux_context(:seluser, nil).should be_nil end it "should return nil if the context is 'unlabeled'" do parse_selinux_context(:seluser, "unlabeled").should be_nil end it "should return the user type when called with :seluser" do parse_selinux_context(:seluser, "user_u:role_r:type_t:s0").should == "user_u" parse_selinux_context(:seluser, "user-withdash_u:role_r:type_t:s0").should == "user-withdash_u" end it "should return the role type when called with :selrole" do parse_selinux_context(:selrole, "user_u:role_r:type_t:s0").should == "role_r" parse_selinux_context(:selrole, "user_u:role-withdash_r:type_t:s0").should == "role-withdash_r" end it "should return the type type when called with :seltype" do parse_selinux_context(:seltype, "user_u:role_r:type_t:s0").should == "type_t" parse_selinux_context(:seltype, "user_u:role_r:type-withdash_t:s0").should == "type-withdash_t" end describe "with spaces in the components" do it "should raise when user contains a space" do expect{parse_selinux_context(:seluser, "user with space_u:role_r:type_t:s0")}.to raise_error Puppet::Error end it "should raise when role contains a space" do expect{parse_selinux_context(:selrole, "user_u:role with space_r:type_t:s0")}.to raise_error Puppet::Error end it "should raise when type contains a space" do expect{parse_selinux_context(:seltype, "user_u:role_r:type with space_t:s0")}.to raise_error Puppet::Error end it "should return the range when range contains a space" do parse_selinux_context(:selrange, "user_u:role_r:type_t:s0 s1").should == "s0 s1" end end it "should return nil for :selrange when no range is returned" do parse_selinux_context(:selrange, "user_u:role_r:type_t").should be_nil end it "should return the range type when called with :selrange" do parse_selinux_context(:selrange, "user_u:role_r:type_t:s0").should == "s0" parse_selinux_context(:selrange, "user_u:role_r:type-withdash_t:s0").should == "s0" end describe "with a variety of SELinux range formats" do ['s0', 's0:c3', 's0:c3.c123', 's0:c3,c5,c8', 'TopSecret', 'TopSecret,Classified', 'Patient_Record'].each do |range| it "should parse range '#{range}'" do parse_selinux_context(:selrange, "user_u:role_r:type_t:#{range}").should == range end end end end describe "set_selinux_context" do before :each do fh = stub 'fh', :close => nil File.stubs(:open).with("/proc/mounts").returns fh fh.stubs(:read_nonblock).returns( "rootfs / rootfs rw 0 0\n/dev/root / ext3 rw,relatime,errors=continue,user_xattr,acl,data=ordered 0 0\n"+ "/dev /dev tmpfs rw,relatime,mode=755 0 0\n/proc /proc proc rw,relatime 0 0\n"+ "/sys /sys sysfs rw,relatime 0 0\n" ).then.raises EOFError end it "should return nil if there is no SELinux support" do self.expects(:selinux_support?).returns false set_selinux_context("/foo", "user_u:role_r:type_t:s0").should be_nil end it "should return nil if selinux_label_support returns false" do self.expects(:selinux_support?).returns true self.expects(:selinux_label_support?).with("/foo").returns false set_selinux_context("/foo", "user_u:role_r:type_t:s0").should be_nil end it "should use lsetfilecon to set a context" do self.expects(:selinux_support?).returns true Selinux.expects(:lsetfilecon).with("/foo", "user_u:role_r:type_t:s0").returns 0 - set_selinux_context("/foo", "user_u:role_r:type_t:s0").should be_true + set_selinux_context("/foo", "user_u:role_r:type_t:s0").should be_truthy end it "should use lsetfilecon to set user_u user context" do self.expects(:selinux_support?).returns true Selinux.expects(:lgetfilecon).with("/foo").returns [0, "foo:role_r:type_t:s0"] Selinux.expects(:lsetfilecon).with("/foo", "user_u:role_r:type_t:s0").returns 0 - set_selinux_context("/foo", "user_u", :seluser).should be_true + set_selinux_context("/foo", "user_u", :seluser).should be_truthy end it "should use lsetfilecon to set role_r role context" do self.expects(:selinux_support?).returns true Selinux.expects(:lgetfilecon).with("/foo").returns [0, "user_u:foo:type_t:s0"] Selinux.expects(:lsetfilecon).with("/foo", "user_u:role_r:type_t:s0").returns 0 - set_selinux_context("/foo", "role_r", :selrole).should be_true + set_selinux_context("/foo", "role_r", :selrole).should be_truthy end it "should use lsetfilecon to set type_t type context" do self.expects(:selinux_support?).returns true Selinux.expects(:lgetfilecon).with("/foo").returns [0, "user_u:role_r:foo:s0"] Selinux.expects(:lsetfilecon).with("/foo", "user_u:role_r:type_t:s0").returns 0 - set_selinux_context("/foo", "type_t", :seltype).should be_true + set_selinux_context("/foo", "type_t", :seltype).should be_truthy end it "should use lsetfilecon to set s0:c3,c5 range context" do self.expects(:selinux_support?).returns true Selinux.expects(:lgetfilecon).with("/foo").returns [0, "user_u:role_r:type_t:s0"] Selinux.expects(:lsetfilecon).with("/foo", "user_u:role_r:type_t:s0:c3,c5").returns 0 - set_selinux_context("/foo", "s0:c3,c5", :selrange).should be_true + set_selinux_context("/foo", "s0:c3,c5", :selrange).should be_truthy end end describe "set_selinux_default_context" do it "should return nil if there is no SELinux support" do self.expects(:selinux_support?).returns false set_selinux_default_context("/foo").should be_nil end it "should return nil if no default context exists" do self.expects(:get_selinux_default_context).with("/foo").returns nil set_selinux_default_context("/foo").should be_nil end it "should do nothing and return nil if the current context matches the default context" do self.expects(:get_selinux_default_context).with("/foo").returns "user_u:role_r:type_t" self.expects(:get_selinux_current_context).with("/foo").returns "user_u:role_r:type_t" set_selinux_default_context("/foo").should be_nil end it "should set and return the default context if current and default do not match" do self.expects(:get_selinux_default_context).with("/foo").returns "user_u:role_r:type_t" self.expects(:get_selinux_current_context).with("/foo").returns "olduser_u:role_r:type_t" self.expects(:set_selinux_context).with("/foo", "user_u:role_r:type_t").returns true set_selinux_default_context("/foo").should == "user_u:role_r:type_t" end end end diff --git a/spec/unit/util/storage_spec.rb b/spec/unit/util/storage_spec.rb index fe6b422c9..25fafa018 100755 --- a/spec/unit/util/storage_spec.rb +++ b/spec/unit/util/storage_spec.rb @@ -1,210 +1,210 @@ #! /usr/bin/env ruby require 'spec_helper' require 'yaml' require 'fileutils' require 'puppet/util/storage' describe Puppet::Util::Storage do include PuppetSpec::Files before(:each) do @basepath = File.expand_path("/somepath") end describe "when caching a symbol" do it "should return an empty hash" do Puppet::Util::Storage.cache(:yayness).should == {} Puppet::Util::Storage.cache(:more_yayness).should == {} end it "should add the symbol to its internal state" do Puppet::Util::Storage.cache(:yayness) Puppet::Util::Storage.state.should == {:yayness=>{}} end it "should not clobber existing state when caching additional objects" do Puppet::Util::Storage.cache(:yayness) Puppet::Util::Storage.state.should == {:yayness=>{}} Puppet::Util::Storage.cache(:bubblyness) Puppet::Util::Storage.state.should == {:yayness=>{},:bubblyness=>{}} end end describe "when caching a Puppet::Type" do before(:each) do @file_test = Puppet::Type.type(:file).new(:name => @basepath+"/yayness", :audit => %w{checksum type}) @exec_test = Puppet::Type.type(:exec).new(:name => @basepath+"/bin/ls /yayness") end it "should return an empty hash" do Puppet::Util::Storage.cache(@file_test).should == {} Puppet::Util::Storage.cache(@exec_test).should == {} end it "should add the resource ref to its internal state" do Puppet::Util::Storage.state.should == {} Puppet::Util::Storage.cache(@file_test) Puppet::Util::Storage.state.should == {"File[#{@basepath}/yayness]"=>{}} Puppet::Util::Storage.cache(@exec_test) Puppet::Util::Storage.state.should == {"File[#{@basepath}/yayness]"=>{}, "Exec[#{@basepath}/bin/ls /yayness]"=>{}} end end describe "when caching something other than a resource or symbol" do it "should cache by converting to a string" do data = Puppet::Util::Storage.cache(42) data[:yay] = true - Puppet::Util::Storage.cache("42")[:yay].should be_true + Puppet::Util::Storage.cache("42")[:yay].should be_truthy end end it "should clear its internal state when clear() is called" do Puppet::Util::Storage.cache(:yayness) Puppet::Util::Storage.state.should == {:yayness=>{}} Puppet::Util::Storage.clear Puppet::Util::Storage.state.should == {} end describe "when loading from the state file" do before do Puppet.settings.stubs(:use).returns(true) end describe "when the state file/directory does not exist" do before(:each) do @path = tmpfile('storage_test') end it "should not fail to load" do - Puppet::FileSystem.exist?(@path).should be_false + Puppet::FileSystem.exist?(@path).should be_falsey Puppet[:statedir] = @path Puppet::Util::Storage.load Puppet[:statefile] = @path Puppet::Util::Storage.load end it "should not lose its internal state when load() is called" do - Puppet::FileSystem.exist?(@path).should be_false + Puppet::FileSystem.exist?(@path).should be_falsey Puppet::Util::Storage.cache(:yayness) Puppet::Util::Storage.state.should == {:yayness=>{}} Puppet[:statefile] = @path Puppet::Util::Storage.load Puppet::Util::Storage.state.should == {:yayness=>{}} end end describe "when the state file/directory exists" do before(:each) do @state_file = tmpfile('storage_test') FileUtils.touch(@state_file) Puppet[:statefile] = @state_file end def write_state_file(contents) File.open(@state_file, 'w') { |f| f.write(contents) } end it "should overwrite its internal state if load() is called" do # Should the state be overwritten even if Puppet[:statefile] is not valid YAML? Puppet::Util::Storage.cache(:yayness) Puppet::Util::Storage.state.should == {:yayness=>{}} Puppet::Util::Storage.load Puppet::Util::Storage.state.should == {} end it "should restore its internal state if the state file contains valid YAML" do test_yaml = {'File["/yayness"]'=>{"name"=>{:a=>:b,:c=>:d}}} write_state_file(test_yaml.to_yaml) Puppet::Util::Storage.load Puppet::Util::Storage.state.should == test_yaml end it "should initialize with a clear internal state if the state file does not contain valid YAML" do write_state_file('{ invalid') Puppet::Util::Storage.load Puppet::Util::Storage.state.should == {} end it "should initialize with a clear internal state if the state file does not contain a hash of data" do write_state_file("not_a_hash") Puppet::Util::Storage.load Puppet::Util::Storage.state.should == {} end it "should raise an error if the state file does not contain valid YAML and cannot be renamed" do write_state_file('{ invalid') File.expects(:rename).raises(SystemCallError) expect { Puppet::Util::Storage.load }.to raise_error(Puppet::Error, /Could not rename/) end it "should attempt to rename the state file if the file is corrupted" do write_state_file('{ invalid') File.expects(:rename).at_least_once Puppet::Util::Storage.load end it "should fail gracefully on load() if the state file is not a regular file" do FileUtils.rm_f(@state_file) Dir.mkdir(@state_file) Puppet::Util::Storage.load end end end describe "when storing to the state file" do before(:each) do @state_file = tmpfile('storage_test') @saved_statefile = Puppet[:statefile] Puppet[:statefile] = @state_file end it "should create the state file if it does not exist" do - Puppet::FileSystem.exist?(Puppet[:statefile]).should be_false + Puppet::FileSystem.exist?(Puppet[:statefile]).should be_falsey Puppet::Util::Storage.cache(:yayness) Puppet::Util::Storage.store - Puppet::FileSystem.exist?(Puppet[:statefile]).should be_true + Puppet::FileSystem.exist?(Puppet[:statefile]).should be_truthy end it "should raise an exception if the state file is not a regular file" do Dir.mkdir(Puppet[:statefile]) Puppet::Util::Storage.cache(:yayness) expect { Puppet::Util::Storage.store }.to raise_error Dir.rmdir(Puppet[:statefile]) end it "should load() the same information that it store()s" do Puppet::Util::Storage.cache(:yayness) Puppet::Util::Storage.state.should == {:yayness=>{}} Puppet::Util::Storage.store Puppet::Util::Storage.clear Puppet::Util::Storage.state.should == {} Puppet::Util::Storage.load Puppet::Util::Storage.state.should == {:yayness=>{}} end end end diff --git a/spec/unit/util/suidmanager_spec.rb b/spec/unit/util/suidmanager_spec.rb index cfe10f2a1..7dd8c262c 100755 --- a/spec/unit/util/suidmanager_spec.rb +++ b/spec/unit/util/suidmanager_spec.rb @@ -1,286 +1,286 @@ #! /usr/bin/env ruby 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" do Process.stubs(:egid).returns(51).then.returns(51).then.returns(user[:gid]) Process.stubs(:euid).returns(50).then.returns(50).then.returns(user[:uid]) 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 + yielded.should be_truthy 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 + yielded.should be_truthy 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 + yielded.should be_truthy 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 + yielded.should be_truthy 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 + yielded.should be_truthy 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 it "should not change_privilege when gid already matches" do Process::GID.expects(:change_privilege).with do |gid| Process.gid = 42 Process.egid = 42 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 it "should not change_privilege when uid already matches" do Process::UID.expects(:change_privilege).with do |uid| Process.uid = 42 Process.euid = 42 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 "#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'] }.to raise_error(Errno::EINVAL) end end diff --git a/spec/unit/util/symbolic_file_mode_spec.rb b/spec/unit/util/symbolic_file_mode_spec.rb index 5346c3a08..2b45eabe7 100755 --- a/spec/unit/util/symbolic_file_mode_spec.rb +++ b/spec/unit/util/symbolic_file_mode_spec.rb @@ -1,182 +1,182 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/symbolic_file_mode' describe Puppet::Util::SymbolicFileMode do include Puppet::Util::SymbolicFileMode describe "#valid_symbolic_mode?" do %w{ 0 0000 1 1 7 11 77 111 777 11 0 00000 01 01 07 011 077 0111 0777 011 = - + u= g= o= a= u+ g+ o+ a+ u- g- o- a- ugo= ugoa= ugugug= a=,u=,g= a=,g+ =rwx +rwx -rwx 644 go-w =rw,+X +X 755 u=rwx,go=rx u=rwx,go=u-w go= g=u-w 755 0755 }.each do |input| it "should treat #{input.inspect} as valid" do - valid_symbolic_mode?(input).should be_true + valid_symbolic_mode?(input).should be_truthy end end [0000, 0111, 0640, 0755, 0777].each do |input| it "should treat the int #{input.to_s(8)} as value" do - valid_symbolic_mode?(input).should be_true + valid_symbolic_mode?(input).should be_truthy end end %w{ -1 -8 8 9 18 19 91 81 000000 11111 77777 0-1 0-8 08 09 018 019 091 081 0000000 011111 077777 u g o a ug uo ua ag }.each do |input| it "should treat #{input.inspect} as invalid" do - valid_symbolic_mode?(input).should be_false + valid_symbolic_mode?(input).should be_falsey end end end describe "#normalize_symbolic_mode" do it "should turn an int into a string" do normalize_symbolic_mode(12).should be_an_instance_of String end it "should not add a leading zero to an int" do normalize_symbolic_mode(12).should_not =~ /^0/ end it "should not add a leading zero to a string with a number" do normalize_symbolic_mode("12").should_not =~ /^0/ end it "should string a leading zero from a number" do normalize_symbolic_mode("012").should == '12' end it "should pass through any other string" do normalize_symbolic_mode("u=rwx").should == 'u=rwx' end end describe "#symbolic_mode_to_int" do { "0654" => 00654, "u+r" => 00400, "g+r" => 00040, "a+r" => 00444, "a+x" => 00111, "o+t" => 01000, "o+t" => 01000, ["o-t", 07777] => 06777, ["a-x", 07777] => 07666, ["a-rwx", 07777] => 07000, ["ug-rwx", 07777] => 07007, "a+x,ug-rwx" => 00001, # My experimentation on debian suggests that +g ignores the sgid flag ["a+g", 02060] => 02666, # My experimentation on debian suggests that -g ignores the sgid flag ["a-g", 02666] => 02000, "g+x,a+g" => 00111, # +X without exec set in the original should not set anything "u+x,g+X" => 00100, "g+X" => 00000, # +X only refers to the original, *unmodified* file mode! ["u+x,a+X", 0600] => 00700, # Examples from the MacOS chmod(1) manpage "0644" => 00644, ["go-w", 07777] => 07755, ["=rw,+X", 07777] => 07777, ["=rw,+X", 07766] => 07777, ["=rw,+X", 07676] => 07777, ["=rw,+X", 07667] => 07777, ["=rw,+X", 07666] => 07666, "0755" => 00755, "u=rwx,go=rx" => 00755, "u=rwx,go=u-w" => 00755, ["go=", 07777] => 07700, ["g=u-w", 07777] => 07757, ["g=u-w", 00700] => 00750, ["g=u-w", 00600] => 00640, ["g=u-w", 00500] => 00550, ["g=u-w", 00400] => 00440, ["g=u-w", 00300] => 00310, ["g=u-w", 00200] => 00200, ["g=u-w", 00100] => 00110, ["g=u-w", 00000] => 00000, # Cruel, but legal, use of the action set. ["g=u+r-w", 0300] => 00350, # Empty assignments. ["u=", 00000] => 00000, ["u=", 00600] => 00000, ["ug=", 00000] => 00000, ["ug=", 00600] => 00000, ["ug=", 00660] => 00000, ["ug=", 00666] => 00006, ["=", 00000] => 00000, ["=", 00666] => 00000, ["+", 00000] => 00000, ["+", 00124] => 00124, ["-", 00000] => 00000, ["-", 00124] => 00124, }.each do |input, result| from = input.is_a?(Array) ? "#{input[0]}, 0#{input[1].to_s(8)}" : input it "should map #{from.inspect} to #{result.inspect}" do symbolic_mode_to_int(*input).should == result end end # Now, test some failure modes. it "should fail if no mode is given" do expect { symbolic_mode_to_int('') }. to raise_error Puppet::Error, /empty mode string/ end %w{u g o ug uo go ugo a uu u/x u!x u=r,,g=r}.each do |input| it "should fail if no (valid) action is given: #{input.inspect}" do expect { symbolic_mode_to_int(input) }. to raise_error Puppet::Error, /Missing action/ end end %w{u+q u-rwF u+rw,g+rw,o+RW}.each do |input| it "should fail with unknown op #{input.inspect}" do expect { symbolic_mode_to_int(input) }. to raise_error Puppet::Error, /Unknown operation/ end end it "should refuse to subtract the conditional execute op" do expect { symbolic_mode_to_int("o-rwX") }. to raise_error Puppet::Error, /only works with/ end it "should refuse to set to the conditional execute op" do expect { symbolic_mode_to_int("o=rwX") }. to raise_error Puppet::Error, /only works with/ end %w{8 08 9 09 118 119}.each do |input| it "should fail for decimal modes: #{input.inspect}" do expect { symbolic_mode_to_int(input) }. to raise_error Puppet::Error, /octal/ end end it "should set the execute bit on a directory, without exec in original" do symbolic_mode_to_int("u+X", 0444, true).to_s(8).should == "544" symbolic_mode_to_int("g+X", 0444, true).to_s(8).should == "454" symbolic_mode_to_int("o+X", 0444, true).to_s(8).should == "445" symbolic_mode_to_int("+X", 0444, true).to_s(8).should == "555" end it "should set the execute bit on a file with exec in the original" do symbolic_mode_to_int("+X", 0544).to_s(8).should == "555" end it "should not set the execute bit on a file without exec on the original even if set by earlier DSL" do symbolic_mode_to_int("u+x,go+X", 0444).to_s(8).should == "544" end end end diff --git a/spec/unit/util/windows/adsi_spec.rb b/spec/unit/util/windows/adsi_spec.rb index 593c73256..60d69305a 100755 --- a/spec/unit/util/windows/adsi_spec.rb +++ b/spec/unit/util/windows/adsi_spec.rb @@ -1,395 +1,395 @@ #!/usr/bin/env ruby require 'spec_helper' require 'puppet/util/windows' describe Puppet::Util::Windows::ADSI, :if => Puppet.features.microsoft_windows? do let(:connection) { stub 'connection' } before(:each) do Puppet::Util::Windows::ADSI.instance_variable_set(:@computer_name, 'testcomputername') Puppet::Util::Windows::ADSI.stubs(:connect).returns connection end after(:each) do Puppet::Util::Windows::ADSI.instance_variable_set(:@computer_name, nil) end it "should generate the correct URI for a resource" do Puppet::Util::Windows::ADSI.uri('test', 'user').should == "WinNT://./test,user" end it "should be able to get the name of the computer" do Puppet::Util::Windows::ADSI.computer_name.should == 'testcomputername' end it "should be able to provide the correct WinNT base URI for the computer" do Puppet::Util::Windows::ADSI.computer_uri.should == "WinNT://." end it "should generate a fully qualified WinNT URI" do Puppet::Util::Windows::ADSI.computer_uri('testcomputername').should == "WinNT://testcomputername" end describe ".computer_name" do it "should return a non-empty ComputerName string" do Puppet::Util::Windows::ADSI.instance_variable_set(:@computer_name, nil) Puppet::Util::Windows::ADSI.computer_name.should_not be_empty end end describe ".sid_uri" do it "should raise an error when the input is not a SID object" do [Object.new, {}, 1, :symbol, '', nil].each do |input| expect { Puppet::Util::Windows::ADSI.sid_uri(input) }.to raise_error(Puppet::Error, /Must use a valid SID object/) end end it "should return a SID uri for a well-known SID (SYSTEM)" do sid = Win32::Security::SID.new('SYSTEM') Puppet::Util::Windows::ADSI.sid_uri(sid).should == 'WinNT://S-1-5-18' end end describe Puppet::Util::Windows::ADSI::User do let(:username) { 'testuser' } let(:domain) { 'DOMAIN' } let(:domain_username) { "#{domain}\\#{username}"} it "should generate the correct URI" do Puppet::Util::Windows::ADSI.stubs(:sid_uri_safe).returns(nil) Puppet::Util::Windows::ADSI::User.uri(username).should == "WinNT://./#{username},user" end it "should generate the correct URI for a user with a domain" do Puppet::Util::Windows::ADSI.stubs(:sid_uri_safe).returns(nil) Puppet::Util::Windows::ADSI::User.uri(username, domain).should == "WinNT://#{domain}/#{username},user" end it "should be able to parse a username without a domain" do Puppet::Util::Windows::ADSI::User.parse_name(username).should == [username, '.'] end it "should be able to parse a username with a domain" do Puppet::Util::Windows::ADSI::User.parse_name(domain_username).should == [username, domain] end it "should raise an error with a username that contains a /" do expect { Puppet::Util::Windows::ADSI::User.parse_name("#{domain}/#{username}") }.to raise_error(Puppet::Error, /Value must be in DOMAIN\\user style syntax/) end it "should be able to create a user" do adsi_user = stub('adsi') connection.expects(:Create).with('user', username).returns(adsi_user) Puppet::Util::Windows::ADSI::Group.expects(:exists?).with(username).returns(false) user = Puppet::Util::Windows::ADSI::User.create(username) user.should be_a(Puppet::Util::Windows::ADSI::User) user.native_user.should == adsi_user end it "should be able to check the existence of a user" do Puppet::Util::Windows::ADSI.stubs(:sid_uri_safe).returns(nil) Puppet::Util::Windows::ADSI.expects(:connect).with("WinNT://./#{username},user").returns connection - Puppet::Util::Windows::ADSI::User.exists?(username).should be_true + Puppet::Util::Windows::ADSI::User.exists?(username).should be_truthy end it "should be able to check the existence of a domain user" do Puppet::Util::Windows::ADSI.stubs(:sid_uri_safe).returns(nil) Puppet::Util::Windows::ADSI.expects(:connect).with("WinNT://#{domain}/#{username},user").returns connection - Puppet::Util::Windows::ADSI::User.exists?(domain_username).should be_true + Puppet::Util::Windows::ADSI::User.exists?(domain_username).should be_truthy end it "should be able to confirm the existence of a user with a well-known SID" do system_user = Win32::Security::SID::LocalSystem # ensure that the underlying OS is queried here Puppet::Util::Windows::ADSI.unstub(:connect) - Puppet::Util::Windows::ADSI::User.exists?(system_user).should be_true + Puppet::Util::Windows::ADSI::User.exists?(system_user).should be_truthy end it "should return nil with an unknown SID" do bogus_sid = 'S-1-2-3-4' # ensure that the underlying OS is queried here Puppet::Util::Windows::ADSI.unstub(:connect) - Puppet::Util::Windows::ADSI::User.exists?(bogus_sid).should be_false + Puppet::Util::Windows::ADSI::User.exists?(bogus_sid).should be_falsey end it "should be able to delete a user" do connection.expects(:Delete).with('user', username) Puppet::Util::Windows::ADSI::User.delete(username) end it "should return an enumeration of IADsUser wrapped objects" do Puppet::Util::Windows::ADSI.stubs(:sid_uri_safe).returns(nil) name = 'Administrator' wmi_users = [stub('WMI', :name => name)] Puppet::Util::Windows::ADSI.expects(:execquery).with('select name from win32_useraccount where localaccount = "TRUE"').returns(wmi_users) native_user = stub('IADsUser') homedir = "C:\\Users\\#{name}" native_user.expects(:Get).with('HomeDirectory').returns(homedir) Puppet::Util::Windows::ADSI.expects(:connect).with("WinNT://./#{name},user").returns(native_user) users = Puppet::Util::Windows::ADSI::User.to_a users.length.should == 1 users[0].name.should == name users[0]['HomeDirectory'].should == homedir end describe "an instance" do let(:adsi_user) { stub('user', :objectSID => []) } let(:sid) { stub(:account => username, :domain => 'testcomputername') } let(:user) { Puppet::Util::Windows::ADSI::User.new(username, adsi_user) } it "should provide its groups as a list of names" do names = ["group1", "group2"] groups = names.map { |name| mock('group', :Name => name) } adsi_user.expects(:Groups).returns(groups) user.groups.should =~ names end it "should be able to test whether a given password is correct" do Puppet::Util::Windows::ADSI::User.expects(:logon).with(username, 'pwdwrong').returns(false) Puppet::Util::Windows::ADSI::User.expects(:logon).with(username, 'pwdright').returns(true) - user.password_is?('pwdwrong').should be_false - user.password_is?('pwdright').should be_true + user.password_is?('pwdwrong').should be_falsey + user.password_is?('pwdright').should be_truthy end it "should be able to set a password" do adsi_user.expects(:SetPassword).with('pwd') adsi_user.expects(:SetInfo).at_least_once flagname = "UserFlags" fADS_UF_DONT_EXPIRE_PASSWD = 0x10000 adsi_user.expects(:Get).with(flagname).returns(0) adsi_user.expects(:Put).with(flagname, fADS_UF_DONT_EXPIRE_PASSWD) user.password = 'pwd' end it "should generate the correct URI" do Puppet::Util::Windows::SID.stubs(:octet_string_to_sid_object).returns(sid) user.uri.should == "WinNT://testcomputername/#{username},user" end describe "when given a set of groups to which to add the user" do let(:groups_to_set) { 'group1,group2' } before(:each) do Puppet::Util::Windows::SID.stubs(:octet_string_to_sid_object).returns(sid) user.expects(:groups).returns ['group2', 'group3'] end describe "if membership is specified as inclusive" do it "should add the user to those groups, and remove it from groups not in the list" do group1 = stub 'group1' group1.expects(:Add).with("WinNT://testcomputername/#{username},user") group3 = stub 'group1' group3.expects(:Remove).with("WinNT://testcomputername/#{username},user") Puppet::Util::Windows::ADSI.expects(:sid_uri).with(sid).returns("WinNT://testcomputername/#{username},user").twice Puppet::Util::Windows::ADSI.expects(:connect).with('WinNT://./group1,group').returns group1 Puppet::Util::Windows::ADSI.expects(:connect).with('WinNT://./group3,group').returns group3 user.set_groups(groups_to_set, false) end end describe "if membership is specified as minimum" do it "should add the user to the specified groups without affecting its other memberships" do group1 = stub 'group1' group1.expects(:Add).with("WinNT://testcomputername/#{username},user") Puppet::Util::Windows::ADSI.expects(:sid_uri).with(sid).returns("WinNT://testcomputername/#{username},user") Puppet::Util::Windows::ADSI.expects(:connect).with('WinNT://./group1,group').returns group1 user.set_groups(groups_to_set, true) end end end end end describe Puppet::Util::Windows::ADSI::Group do let(:groupname) { 'testgroup' } describe "an instance" do let(:adsi_group) { stub 'group' } let(:group) { Puppet::Util::Windows::ADSI::Group.new(groupname, adsi_group) } let(:someone_sid){ stub(:account => 'someone', :domain => 'testcomputername')} describe "should be able to use SID objects" do let(:system) { Puppet::Util::Windows::SID.name_to_sid_object('SYSTEM') } let(:invalid) { Puppet::Util::Windows::SID.name_to_sid_object('foobar') } it "to add a member" do adsi_group.expects(:Add).with("WinNT://S-1-5-18") group.add_member_sids(system) end it "and raise when passed a non-SID object to add" do expect{ group.add_member_sids(invalid)}.to raise_error(Puppet::Error, /Must use a valid SID object/) end it "to remove a member" do adsi_group.expects(:Remove).with("WinNT://S-1-5-18") group.remove_member_sids(system) end it "and raise when passed a non-SID object to remove" do expect{ group.remove_member_sids(invalid)}.to raise_error(Puppet::Error, /Must use a valid SID object/) end end it "should provide its groups as a list of names" do names = ['user1', 'user2'] users = names.map { |name| mock('user', :Name => name) } adsi_group.expects(:Members).returns(users) group.members.should =~ names end it "should be able to add a list of users to a group" do names = ['DOMAIN\user1', 'user2'] sids = [ stub(:account => 'user1', :domain => 'DOMAIN'), stub(:account => 'user2', :domain => 'testcomputername'), stub(:account => 'user3', :domain => 'DOMAIN2'), ] # use stubbed objectSid on member to return stubbed SID Puppet::Util::Windows::SID.expects(:octet_string_to_sid_object).with([0]).returns(sids[0]) Puppet::Util::Windows::SID.expects(:octet_string_to_sid_object).with([1]).returns(sids[1]) Puppet::Util::Windows::SID.expects(:name_to_sid_object).with('user2').returns(sids[1]) Puppet::Util::Windows::SID.expects(:name_to_sid_object).with('DOMAIN2\user3').returns(sids[2]) Puppet::Util::Windows::ADSI.expects(:sid_uri).with(sids[0]).returns("WinNT://DOMAIN/user1,user") Puppet::Util::Windows::ADSI.expects(:sid_uri).with(sids[2]).returns("WinNT://DOMAIN2/user3,user") members = names.each_with_index.map{|n,i| stub(:Name => n, :objectSID => [i])} adsi_group.expects(:Members).returns members adsi_group.expects(:Remove).with('WinNT://DOMAIN/user1,user') adsi_group.expects(:Add).with('WinNT://DOMAIN2/user3,user') group.set_members(['user2', 'DOMAIN2\user3']) end it "should raise an error when a username does not resolve to a SID" do expect { adsi_group.expects(:Members).returns [] group.set_members(['foobar']) }.to raise_error(Puppet::Error, /Could not resolve username: foobar/) end it "should generate the correct URI" do Puppet::Util::Windows::ADSI.stubs(:sid_uri_safe).returns(nil) group.uri.should == "WinNT://./#{groupname},group" end end it "should generate the correct URI" do Puppet::Util::Windows::ADSI.stubs(:sid_uri_safe).returns(nil) Puppet::Util::Windows::ADSI::Group.uri("people").should == "WinNT://./people,group" end it "should be able to create a group" do adsi_group = stub("adsi") connection.expects(:Create).with('group', groupname).returns(adsi_group) Puppet::Util::Windows::ADSI::User.expects(:exists?).with(groupname).returns(false) group = Puppet::Util::Windows::ADSI::Group.create(groupname) group.should be_a(Puppet::Util::Windows::ADSI::Group) group.native_group.should == adsi_group end it "should be able to confirm the existence of a group" do Puppet::Util::Windows::ADSI.stubs(:sid_uri_safe).returns(nil) Puppet::Util::Windows::ADSI.expects(:connect).with("WinNT://./#{groupname},group").returns connection - Puppet::Util::Windows::ADSI::Group.exists?(groupname).should be_true + Puppet::Util::Windows::ADSI::Group.exists?(groupname).should be_truthy end it "should be able to confirm the existence of a group with a well-known SID" do service_group = Win32::Security::SID::Service # ensure that the underlying OS is queried here Puppet::Util::Windows::ADSI.unstub(:connect) - Puppet::Util::Windows::ADSI::Group.exists?(service_group).should be_true + Puppet::Util::Windows::ADSI::Group.exists?(service_group).should be_truthy end it "should return nil with an unknown SID" do bogus_sid = 'S-1-2-3-4' # ensure that the underlying OS is queried here Puppet::Util::Windows::ADSI.unstub(:connect) - Puppet::Util::Windows::ADSI::Group.exists?(bogus_sid).should be_false + Puppet::Util::Windows::ADSI::Group.exists?(bogus_sid).should be_falsey end it "should be able to delete a group" do connection.expects(:Delete).with('group', groupname) Puppet::Util::Windows::ADSI::Group.delete(groupname) end it "should return an enumeration of IADsGroup wrapped objects" do Puppet::Util::Windows::ADSI.stubs(:sid_uri_safe).returns(nil) name = 'Administrators' wmi_groups = [stub('WMI', :name => name)] Puppet::Util::Windows::ADSI.expects(:execquery).with('select name from win32_group where localaccount = "TRUE"').returns(wmi_groups) native_group = stub('IADsGroup') native_group.expects(:Members).returns([stub(:Name => 'Administrator')]) Puppet::Util::Windows::ADSI.expects(:connect).with("WinNT://./#{name},group").returns(native_group) groups = Puppet::Util::Windows::ADSI::Group.to_a groups.length.should == 1 groups[0].name.should == name groups[0].members.should == ['Administrator'] end end describe Puppet::Util::Windows::ADSI::UserProfile do it "should be able to delete a user profile" do connection.expects(:Delete).with("Win32_UserProfile.SID='S-A-B-C'") Puppet::Util::Windows::ADSI::UserProfile.delete('S-A-B-C') end it "should warn on 2003" do connection.expects(:Delete).raises(RuntimeError, "Delete (WIN32OLERuntimeError) OLE error code:80041010 in SWbemServicesEx Invalid class HRESULT error code:0x80020009 Exception occurred.") Puppet.expects(:warning).with("Cannot delete user profile for 'S-A-B-C' prior to Vista SP1") Puppet::Util::Windows::ADSI::UserProfile.delete('S-A-B-C') end end end diff --git a/spec/unit/util/windows/sid_spec.rb b/spec/unit/util/windows/sid_spec.rb index 1e9f44070..5b78b1a26 100755 --- a/spec/unit/util/windows/sid_spec.rb +++ b/spec/unit/util/windows/sid_spec.rb @@ -1,176 +1,176 @@ #!/usr/bin/env ruby require 'spec_helper' describe "Puppet::Util::Windows::SID", :if => Puppet.features.microsoft_windows? do if Puppet.features.microsoft_windows? require 'puppet/util/windows' end let(:subject) { Puppet::Util::Windows::SID } let(:sid) { Win32::Security::SID::LocalSystem } let(:invalid_sid) { 'bogus' } let(:unknown_sid) { 'S-0-0-0' } let(:unknown_name) { 'chewbacca' } context "#octet_string_to_sid_object" do it "should properly convert an array of bytes for the local Administrator SID" do host = '.' username = 'Administrator' admin = WIN32OLE.connect("WinNT://#{host}/#{username},user") converted = subject.octet_string_to_sid_object(admin.objectSID) converted.should == Win32::Security::SID.new(username, host) converted.should be_an_instance_of Win32::Security::SID end it "should properly convert an array of bytes for a well-known SID" do bytes = [1, 1, 0, 0, 0, 0, 0, 5, 18, 0, 0, 0] converted = subject.octet_string_to_sid_object(bytes) converted.should == Win32::Security::SID.new('SYSTEM') converted.should be_an_instance_of Win32::Security::SID end it "should raise an error for non-array input" do expect { subject.octet_string_to_sid_object(invalid_sid) }.to raise_error(Puppet::Error, /Octet string must be an array of bytes/) end it "should raise an error for an empty byte array" do expect { subject.octet_string_to_sid_object([]) }.to raise_error(Puppet::Error, /Octet string must be an array of bytes/) end it "should raise an error for a malformed byte array" do expect { invalid_octet = [1] subject.octet_string_to_sid_object(invalid_octet) }.to raise_error(SystemCallError, /No mapping between account names and security IDs was done./) end end context "#name_to_sid" do it "should return nil if the account does not exist" do subject.name_to_sid(unknown_name).should be_nil end it "should accept unqualified account name" do subject.name_to_sid('SYSTEM').should == sid end it "should return a SID for a passed user or group name" do subject.expects(:name_to_sid_object).with('testers').returns 'S-1-5-32-547' subject.name_to_sid('testers').should == 'S-1-5-32-547' end it "should return a SID for a passed fully-qualified user or group name" do subject.expects(:name_to_sid_object).with('MACHINE\testers').returns 'S-1-5-32-547' subject.name_to_sid('MACHINE\testers').should == 'S-1-5-32-547' end it "should be case-insensitive" do subject.name_to_sid('SYSTEM').should == subject.name_to_sid('system') end it "should be leading and trailing whitespace-insensitive" do subject.name_to_sid('SYSTEM').should == subject.name_to_sid(' SYSTEM ') end it "should accept domain qualified account names" do subject.name_to_sid('NT AUTHORITY\SYSTEM').should == sid end it "should be the identity function for any sid" do subject.name_to_sid(sid).should == sid end end context "#name_to_sid_object" do it "should return nil if the account does not exist" do subject.name_to_sid_object(unknown_name).should be_nil end it "should return a Win32::Security::SID instance for any valid sid" do subject.name_to_sid_object(sid).should be_an_instance_of(Win32::Security::SID) end it "should accept unqualified account name" do subject.name_to_sid_object('SYSTEM').to_s.should == sid end it "should be case-insensitive" do subject.name_to_sid_object('SYSTEM').should == subject.name_to_sid_object('system') end it "should be leading and trailing whitespace-insensitive" do subject.name_to_sid_object('SYSTEM').should == subject.name_to_sid_object(' SYSTEM ') end it "should accept domain qualified account names" do subject.name_to_sid_object('NT AUTHORITY\SYSTEM').to_s.should == sid end end context "#sid_to_name" do it "should return nil if given a sid for an account that doesn't exist" do subject.sid_to_name(unknown_sid).should be_nil end it "should accept a sid" do subject.sid_to_name(sid).should == "NT AUTHORITY\\SYSTEM" end end context "#sid_ptr_to_string" do it "should raise if given an invalid sid" do expect { subject.sid_ptr_to_string(nil) }.to raise_error(Puppet::Error, /Invalid SID/) end it "should yield a valid sid pointer" do string = nil subject.string_to_sid_ptr(sid) do |ptr| string = subject.sid_ptr_to_string(ptr) end string.should == sid end end context "#string_to_sid_ptr" do it "should yield sid_ptr" do ptr = nil subject.string_to_sid_ptr(sid) do |p| ptr = p end ptr.should_not be_nil end it "should raise on an invalid sid" do expect { subject.string_to_sid_ptr(invalid_sid) }.to raise_error(Puppet::Error, /Failed to convert string SID/) end end context "#valid_sid?" do it "should return true for a valid SID" do - subject.valid_sid?(sid).should be_true + subject.valid_sid?(sid).should be_truthy end it "should return false for an invalid SID" do - subject.valid_sid?(invalid_sid).should be_false + subject.valid_sid?(invalid_sid).should be_falsey end it "should raise if the conversion fails" do subject.expects(:string_to_sid_ptr).with(sid). raises(Puppet::Util::Windows::Error.new("Failed to convert string SID: #{sid}", Puppet::Util::Windows::Error::ERROR_ACCESS_DENIED)) expect { subject.string_to_sid_ptr(sid) {|ptr| } }.to raise_error(Puppet::Util::Windows::Error, /Failed to convert string SID: #{sid}/) end end end diff --git a/spec/unit/util/windows/string_spec.rb b/spec/unit/util/windows/string_spec.rb index 5c6473e70..3c11726a7 100644 --- a/spec/unit/util/windows/string_spec.rb +++ b/spec/unit/util/windows/string_spec.rb @@ -1,58 +1,58 @@ # encoding: UTF-8 #!/usr/bin/env ruby require 'spec_helper' require 'puppet/util/windows' describe "Puppet::Util::Windows::String", :if => Puppet.features.microsoft_windows? do UTF16_NULL = [0, 0] def wide_string(str) Puppet::Util::Windows::String.wide_string(str) end def converts_to_wide_string(string_value) expected = string_value.encode(Encoding::UTF_16LE) expected_bytes = expected.bytes.to_a + UTF16_NULL wide_string(string_value).bytes.to_a.should == expected_bytes end context "wide_string" do it "should return encoding of UTF-16LE" do wide_string("bob").encoding.should == Encoding::UTF_16LE end it "should return valid encoding" do - wide_string("bob").valid_encoding?.should be_true + wide_string("bob").valid_encoding?.should be_truthy end it "should convert an ASCII string" do converts_to_wide_string("bob".encode(Encoding::US_ASCII)) end it "should convert a UTF-8 string" do converts_to_wide_string("bob".encode(Encoding::UTF_8)) end it "should convert a UTF-16LE string" do converts_to_wide_string("bob\u00E8".encode(Encoding::UTF_16LE)) end it "should convert a UTF-16BE string" do converts_to_wide_string("bob\u00E8".encode(Encoding::UTF_16BE)) end it "should convert an UTF-32LE string" do converts_to_wide_string("bob\u00E8".encode(Encoding::UTF_32LE)) end it "should convert an UTF-32BE string" do converts_to_wide_string("bob\u00E8".encode(Encoding::UTF_32BE)) end it "should return a nil when given a nil" do wide_string(nil).should == nil end end end diff --git a/spec/unit/util/yaml_spec.rb b/spec/unit/util/yaml_spec.rb index a535d79c7..6472dfd44 100644 --- a/spec/unit/util/yaml_spec.rb +++ b/spec/unit/util/yaml_spec.rb @@ -1,55 +1,55 @@ require 'spec_helper' require 'puppet/util/yaml' describe Puppet::Util::Yaml do include PuppetSpec::Files let(:filename) { tmpfile("yaml") } it "reads a YAML file from disk" do write_file(filename, YAML.dump({ "my" => "data" })) expect(Puppet::Util::Yaml.load_file(filename)).to eq({ "my" => "data" }) end it "writes data formatted as YAML to disk" do Puppet::Util::Yaml.dump({ "my" => "data" }, filename) expect(Puppet::Util::Yaml.load_file(filename)).to eq({ "my" => "data" }) end it "raises an error when the file is invalid YAML" do write_file(filename, "{ invalid") expect { Puppet::Util::Yaml.load_file(filename) }.to raise_error(Puppet::Util::Yaml::YamlLoadError) end it "raises an error when the file does not exist" do expect { Puppet::Util::Yaml.load_file("no") }.to raise_error(Puppet::Util::Yaml::YamlLoadError, /No such file or directory/) end it "raises an error when the filename is illegal" do expect { Puppet::Util::Yaml.load_file("not\0allowed") }.to raise_error(Puppet::Util::Yaml::YamlLoadError, /null byte/) end context "when the file is empty" do it "returns false" do Puppet::FileSystem.touch(filename) - expect(Puppet::Util::Yaml.load_file(filename)).to be_false + expect(Puppet::Util::Yaml.load_file(filename)).to be_falsey end it "allows return value to be overridden" do Puppet::FileSystem.touch(filename) expect(Puppet::Util::Yaml.load_file(filename, {})).to eq({}) end end def write_file(name, contents) File.open(name, "w") do |fh| fh.write(contents) end end end diff --git a/spec/unit/util_spec.rb b/spec/unit/util_spec.rb index d59e78a52..dca7ccb5b 100755 --- a/spec/unit/util_spec.rb +++ b/spec/unit/util_spec.rb @@ -1,545 +1,545 @@ #!/usr/bin/env ruby require 'spec_helper' describe Puppet::Util do include PuppetSpec::Files if Puppet.features.microsoft_windows? def set_mode(mode, file) Puppet::Util::Windows::Security.set_mode(mode, file) end def get_mode(file) Puppet::Util::Windows::Security.get_mode(file) & 07777 end else def set_mode(mode, file) File.chmod(mode, file) end def get_mode(file) Puppet::FileSystem.lstat(file).mode & 07777 end end describe "#withenv" do before :each do @original_path = ENV["PATH"] @new_env = {:PATH => "/some/bogus/path"} end it "should change environment variables within the block then reset environment variables to their original values" do Puppet::Util.withenv @new_env do ENV["PATH"].should == "/some/bogus/path" end ENV["PATH"].should == @original_path end it "should reset environment variables to their original values even if the block fails" do begin Puppet::Util.withenv @new_env do ENV["PATH"].should == "/some/bogus/path" raise "This is a failure" end rescue end ENV["PATH"].should == @original_path end it "should reset environment variables even when they are set twice" do # Setting Path & Environment parameters in Exec type can cause weirdness @new_env["PATH"] = "/someother/bogus/path" Puppet::Util.withenv @new_env do # When assigning duplicate keys, can't guarantee order of evaluation ENV["PATH"].should =~ /\/some.*\/bogus\/path/ end ENV["PATH"].should == @original_path end it "should remove any new environment variables after the block ends" do @new_env[:FOO] = "bar" ENV["FOO"] = nil Puppet::Util.withenv @new_env do ENV["FOO"].should == "bar" end ENV["FOO"].should == nil end end describe "#absolute_path?" do describe "on posix systems", :if => Puppet.features.posix? do it "should default to the platform of the local system" do Puppet::Util.should be_absolute_path('/foo') Puppet::Util.should_not be_absolute_path('C:/foo') end end describe "on windows", :if => Puppet.features.microsoft_windows? do it "should default to the platform of the local system" do Puppet::Util.should be_absolute_path('C:/foo') Puppet::Util.should_not be_absolute_path('/foo') end end describe "when using platform :posix" do %w[/ /foo /foo/../bar //foo //Server/Foo/Bar //?/C:/foo/bar /\Server/Foo /foo//bar/baz].each do |path| it "should return true for #{path}" do Puppet::Util.should be_absolute_path(path, :posix) end end %w[. ./foo \foo C:/foo \\Server\Foo\Bar \\?\C:\foo\bar \/?/foo\bar \/Server/foo foo//bar/baz].each do |path| it "should return false for #{path}" do Puppet::Util.should_not be_absolute_path(path, :posix) end end end describe "when using platform :windows" do %w[C:/foo C:\foo \\\\Server\Foo\Bar \\\\?\C:\foo\bar //Server/Foo/Bar //?/C:/foo/bar /\?\C:/foo\bar \/Server\Foo/Bar c:/foo//bar//baz].each do |path| it "should return true for #{path}" do Puppet::Util.should be_absolute_path(path, :windows) end end %w[/ . ./foo \foo /foo /foo/../bar //foo C:foo/bar foo//bar/baz].each do |path| it "should return false for #{path}" do Puppet::Util.should_not be_absolute_path(path, :windows) end end end end describe "#path_to_uri" do %w[. .. foo foo/bar foo/../bar].each do |path| it "should reject relative path: #{path}" do lambda { Puppet::Util.path_to_uri(path) }.should raise_error(Puppet::Error) end end it "should perform URI escaping" do Puppet::Util.path_to_uri("/foo bar").path.should == "/foo%20bar" end describe "when using platform :posix" do before :each do Puppet.features.stubs(:posix).returns true Puppet.features.stubs(:microsoft_windows?).returns false end %w[/ /foo /foo/../bar].each do |path| it "should convert #{path} to URI" do Puppet::Util.path_to_uri(path).path.should == path end end end describe "when using platform :windows" do before :each do Puppet.features.stubs(:posix).returns false Puppet.features.stubs(:microsoft_windows?).returns true end it "should normalize backslashes" do Puppet::Util.path_to_uri('c:\\foo\\bar\\baz').path.should == '/' + 'c:/foo/bar/baz' end %w[C:/ C:/foo/bar].each do |path| it "should convert #{path} to absolute URI" do Puppet::Util.path_to_uri(path).path.should == '/' + path end end %w[share C$].each do |path| it "should convert UNC #{path} to absolute URI" do uri = Puppet::Util.path_to_uri("\\\\server\\#{path}") uri.host.should == 'server' uri.path.should == '/' + path end end end end describe ".uri_to_path" do require 'uri' it "should strip host component" do Puppet::Util.uri_to_path(URI.parse('http://foo/bar')).should == '/bar' end it "should accept puppet URLs" do Puppet::Util.uri_to_path(URI.parse('puppet:///modules/foo')).should == '/modules/foo' end it "should return unencoded path" do Puppet::Util.uri_to_path(URI.parse('http://foo/bar%20baz')).should == '/bar baz' end it "should be nil-safe" do Puppet::Util.uri_to_path(nil).should be_nil end describe "when using platform :posix",:if => Puppet.features.posix? do it "should accept root" do Puppet::Util.uri_to_path(URI.parse('file:/')).should == '/' end it "should accept single slash" do Puppet::Util.uri_to_path(URI.parse('file:/foo/bar')).should == '/foo/bar' end it "should accept triple slashes" do Puppet::Util.uri_to_path(URI.parse('file:///foo/bar')).should == '/foo/bar' end end describe "when using platform :windows", :if => Puppet.features.microsoft_windows? do it "should accept root" do Puppet::Util.uri_to_path(URI.parse('file:/C:/')).should == 'C:/' end it "should accept single slash" do Puppet::Util.uri_to_path(URI.parse('file:/C:/foo/bar')).should == 'C:/foo/bar' end it "should accept triple slashes" do Puppet::Util.uri_to_path(URI.parse('file:///C:/foo/bar')).should == 'C:/foo/bar' end it "should accept file scheme with double slashes as a UNC path" do Puppet::Util.uri_to_path(URI.parse('file://host/share/file')).should == '//host/share/file' end end end describe "safe_posix_fork" do let(:pid) { 5501 } before :each do # Most of the things this method does are bad to do during specs. :/ Kernel.stubs(:fork).returns(pid).yields $stdin.stubs(:reopen) $stdout.stubs(:reopen) $stderr.stubs(:reopen) # ensure that we don't really close anything! (0..256).each {|n| IO.stubs(:new) } end it "should close all open file descriptors except stdin/stdout/stderr" do # This is ugly, but I can't really think of a better way to do it without # letting it actually close fds, which seems risky (0..2).each {|n| IO.expects(:new).with(n).never} (3..256).each {|n| IO.expects(:new).with(n).returns mock('io', :close) } Puppet::Util.safe_posix_fork end it "should fork a child process to execute the block" do Kernel.expects(:fork).returns(pid).yields Puppet::Util.safe_posix_fork do message = "Fork this!" end end it "should return the pid of the child process" do Puppet::Util.safe_posix_fork.should == pid end end describe "#which" do let(:base) { File.expand_path('/bin') } let(:path) { File.join(base, 'foo') } before :each do FileTest.stubs(:file?).returns false FileTest.stubs(:file?).with(path).returns true FileTest.stubs(:executable?).returns false FileTest.stubs(:executable?).with(path).returns true end it "should accept absolute paths" do Puppet::Util.which(path).should == path end it "should return nil if no executable found" do Puppet::Util.which('doesnotexist').should be_nil end it "should warn if the user's HOME is not set but their PATH contains a ~" do env_path = %w[~/bin /usr/bin /bin].join(File::PATH_SEPARATOR) env = {:HOME => nil, :PATH => env_path} env.merge!({:HOMEDRIVE => nil, :USERPROFILE => nil}) if Puppet.features.microsoft_windows? Puppet::Util.withenv(env) do Puppet::Util::Warnings.expects(:warnonce).once Puppet::Util.which('foo') end end it "should reject directories" do Puppet::Util.which(base).should be_nil end it "should ignore ~user directories if the user doesn't exist" do # Windows treats *any* user as a "user that doesn't exist", which means # that this will work correctly across all our platforms, and should # behave consistently. If they ever implement it correctly (eg: to do # the lookup for real) it should just work transparently. baduser = 'if_this_user_exists_I_will_eat_my_hat' Puppet::Util.withenv("PATH" => "~#{baduser}#{File::PATH_SEPARATOR}#{base}") do Puppet::Util.which('foo').should == path end end describe "on POSIX systems" do before :each do Puppet.features.stubs(:posix?).returns true Puppet.features.stubs(:microsoft_windows?).returns false end it "should walk the search PATH returning the first executable" do ENV.stubs(:[]).with('PATH').returns(File.expand_path('/bin')) Puppet::Util.which('foo').should == path end end describe "on Windows systems" do let(:path) { File.expand_path(File.join(base, 'foo.CMD')) } before :each do Puppet.features.stubs(:posix?).returns false Puppet.features.stubs(:microsoft_windows?).returns true end describe "when a file extension is specified" do it "should walk each directory in PATH ignoring PATHEXT" do ENV.stubs(:[]).with('PATH').returns(%w[/bar /bin].map{|dir| File.expand_path(dir)}.join(File::PATH_SEPARATOR)) FileTest.expects(:file?).with(File.join(File.expand_path('/bar'), 'foo.CMD')).returns false ENV.expects(:[]).with('PATHEXT').never Puppet::Util.which('foo.CMD').should == path end end describe "when a file extension is not specified" do it "should walk each extension in PATHEXT until an executable is found" do bar = File.expand_path('/bar') ENV.stubs(:[]).with('PATH').returns("#{bar}#{File::PATH_SEPARATOR}#{base}") ENV.stubs(:[]).with('PATHEXT').returns(".EXE#{File::PATH_SEPARATOR}.CMD") exts = sequence('extensions') FileTest.expects(:file?).in_sequence(exts).with(File.join(bar, 'foo.EXE')).returns false FileTest.expects(:file?).in_sequence(exts).with(File.join(bar, 'foo.CMD')).returns false FileTest.expects(:file?).in_sequence(exts).with(File.join(base, 'foo.EXE')).returns false FileTest.expects(:file?).in_sequence(exts).with(path).returns true Puppet::Util.which('foo').should == path end it "should walk the default extension path if the environment variable is not defined" do ENV.stubs(:[]).with('PATH').returns(base) ENV.stubs(:[]).with('PATHEXT').returns(nil) exts = sequence('extensions') %w[.COM .EXE .BAT].each do |ext| FileTest.expects(:file?).in_sequence(exts).with(File.join(base, "foo#{ext}")).returns false end FileTest.expects(:file?).in_sequence(exts).with(path).returns true Puppet::Util.which('foo').should == path end it "should fall back if no extension matches" do ENV.stubs(:[]).with('PATH').returns(base) ENV.stubs(:[]).with('PATHEXT').returns(".EXE") FileTest.stubs(:file?).with(File.join(base, 'foo.EXE')).returns false FileTest.stubs(:file?).with(File.join(base, 'foo')).returns true FileTest.stubs(:executable?).with(File.join(base, 'foo')).returns true Puppet::Util.which('foo').should == File.join(base, 'foo') end end end end describe "hash symbolizing functions" do let (:myhash) { { "foo" => "bar", :baz => "bam" } } let (:resulthash) { { :foo => "bar", :baz => "bam" } } describe "#symbolizehash" do it "should return a symbolized hash" do newhash = Puppet::Util.symbolizehash(myhash) newhash.should == resulthash end end end context "#replace_file" do subject { Puppet::Util } it { should respond_to :replace_file } let :target do target = Tempfile.new("puppet-util-replace-file") target.puts("hello, world") target.flush # make sure content is on disk. target.fsync rescue nil target.close target end it "should fail if no block is given" do expect { subject.replace_file(target.path, 0600) }.to raise_error /block/ end it "should replace a file when invoked" do # Check that our file has the expected content. File.read(target.path).should == "hello, world\n" # Replace the file. subject.replace_file(target.path, 0600) do |fh| fh.puts "I am the passenger..." end # ...and check the replacement was complete. File.read(target.path).should == "I am the passenger...\n" end # When running with the same user and group sid, which is the default, # Windows collapses the owner and group modes into a single ACE, resulting # in set(0600) => get(0660) and so forth. --daniel 2012-03-30 modes = [0555, 0660, 0770] modes += [0600, 0700] unless Puppet.features.microsoft_windows? modes.each do |mode| it "should copy 0#{mode.to_s(8)} permissions from the target file by default" do set_mode(mode, target.path) get_mode(target.path).should == mode subject.replace_file(target.path, 0000) {|fh| fh.puts "bazam" } get_mode(target.path).should == mode File.read(target.path).should == "bazam\n" end end it "should copy the permissions of the source file before yielding on Unix", :if => !Puppet.features.microsoft_windows? do set_mode(0555, target.path) inode = Puppet::FileSystem.stat(target.path).ino yielded = false subject.replace_file(target.path, 0600) do |fh| get_mode(fh.path).should == 0555 yielded = true end - yielded.should be_true + yielded.should be_truthy Puppet::FileSystem.stat(target.path).ino.should_not == inode get_mode(target.path).should == 0555 end it "should use the default permissions if the source file doesn't exist" do new_target = target.path + '.foo' - Puppet::FileSystem.exist?(new_target).should be_false + Puppet::FileSystem.exist?(new_target).should be_falsey begin subject.replace_file(new_target, 0555) {|fh| fh.puts "foo" } get_mode(new_target).should == 0555 ensure Puppet::FileSystem.unlink(new_target) if Puppet::FileSystem.exist?(new_target) end end it "should not replace the file if an exception is thrown in the block" do yielded = false threw = false begin subject.replace_file(target.path, 0600) do |fh| yielded = true fh.puts "different content written, then..." raise "...throw some random failure" end rescue Exception => e if e.to_s =~ /some random failure/ threw = true else raise end end - yielded.should be_true - threw.should be_true + yielded.should be_truthy + threw.should be_truthy # ...and check the replacement was complete. File.read(target.path).should == "hello, world\n" end {:string => '664', :number => 0664, :symbolic => "ug=rw-,o=r--" }.each do |label,mode| it "should support #{label} format permissions" do new_target = target.path + "#{mode}.foo" - Puppet::FileSystem.exist?(new_target).should be_false + Puppet::FileSystem.exist?(new_target).should be_falsey begin subject.replace_file(new_target, mode) {|fh| fh.puts "this is an interesting content" } get_mode(new_target).should == 0664 ensure Puppet::FileSystem.unlink(new_target) if Puppet::FileSystem.exist?(new_target) end end end end describe "#pretty_backtrace" do it "should include lines that don't match the standard backtrace pattern" do line = "non-standard line\n" trace = caller[0..2] + [line] + caller[3..-1] Puppet::Util.pretty_backtrace(trace).should =~ /#{line}/ end it "should include function names" do Puppet::Util.pretty_backtrace.should =~ /:in `\w+'/ end it "should work with Windows paths" do Puppet::Util.pretty_backtrace(["C:/work/puppet/c.rb:12:in `foo'\n"]). should == "C:/work/puppet/c.rb:12:in `foo'" end end describe "#deterministic_rand" do it "should not fiddle with future rand calls" do Puppet::Util.deterministic_rand(123,20) rand_one = rand() Puppet::Util.deterministic_rand(123,20) rand().should_not eql(rand_one) end if defined?(Random) == 'constant' && Random.class == Class it "should not fiddle with the global seed" do srand(1234) Puppet::Util.deterministic_rand(123,20) srand().should eql(1234) end # ruby below 1.9.2 variant else it "should set a new global seed" do srand(1234) Puppet::Util.deterministic_rand(123,20) srand().should_not eql(1234) end end end end