diff options
Diffstat (limited to 'spec/unit')
192 files changed, 5717 insertions, 2425 deletions
diff --git a/spec/unit/agent_spec.rb b/spec/unit/agent_spec.rb index 536fb47de..38e53176d 100755 --- a/spec/unit/agent_spec.rb +++ b/spec/unit/agent_spec.rb @@ -135,18 +135,6 @@ describe Puppet::Agent do @agent.run end - it "should use a mutex to restrict multi-threading" do - client = AgentTestClient.new - AgentTestClient.expects(:new).returns client - - mutex = mock 'mutex' - @agent.expects(:sync).returns mutex - - mutex.expects(:synchronize) - client.expects(:run).never # if it doesn't run, then we know our yield is what triggers it - @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 diff --git a/spec/unit/application/agent_spec.rb b/spec/unit/application/agent_spec.rb index 2ec32dbfb..4280a953c 100755 --- a/spec/unit/application/agent_spec.rb +++ b/spec/unit/application/agent_spec.rb @@ -198,7 +198,7 @@ describe Puppet::Application::Agent do describe "during setup" do before :each do Puppet.stubs(:info) - FileTest.stubs(:exists?).returns(true) + Puppet::FileSystem::File.stubs(:exist?).returns(true) Puppet[:libdir] = "/dev/null/lib" Puppet::Transaction::Report.indirection.stubs(:terminus_class=) Puppet::Transaction::Report.indirection.stubs(:cache_class=) @@ -449,8 +449,8 @@ describe Puppet::Application::Agent do describe "when setting up listen" do before :each do - FileTest.stubs(:exists?).with('auth').returns(true) - File.stubs(:exist?).returns(true) + Puppet::FileSystem::File.stubs(:exist?).with('auth').returns(true) + Puppet::FileSystem::File.stubs(:exist?).returns(true) @puppetd.options[:serve] = [] @server = stub_everything 'server' Puppet::Network::Server.stubs(:new).returns(@server) @@ -460,7 +460,7 @@ describe Puppet::Application::Agent do it "should exit if no authorization file" do Puppet[:listen] = true Puppet.stubs(:err) - FileTest.stubs(:exists?).with(Puppet[:rest_authconfig]).returns(false) + Puppet::FileSystem::File.stubs(:exist?).with(Puppet[:rest_authconfig]).returns(false) expect do execute_agent diff --git a/spec/unit/application/apply_spec.rb b/spec/unit/application/apply_spec.rb index 97728c06e..d3cc54884 100755 --- a/spec/unit/application/apply_spec.rb +++ b/spec/unit/application/apply_spec.rb @@ -45,8 +45,8 @@ describe Puppet::Application::Apply do @apply.handle_logdest("console") end - it "should put the logset options to true" do - @apply.options.expects(:[]=).with(:logset,true) + it "should set the setdest options to true" do + @apply.options.expects(:[]=).with(:setdest,true) @apply.handle_logdest("console") end @@ -103,6 +103,22 @@ describe Puppet::Application::Apply do @apply.setup end + it "configures a profiler when profiling is enabled" do + Puppet[:profile] = true + + @apply.setup + + expect(Puppet::Util::Profiler.current).to be_a(Puppet::Util::Profiler::WallClock) + end + + it "does not have a profiler if profiling is disabled" do + Puppet[:profile] = false + + @apply.setup + + expect(Puppet::Util::Profiler.current).to eq(Puppet::Util::Profiler::NONE) + end + it "should set default_file_terminus to `file_server` to be local" do @apply.app_defaults[:default_file_terminus].should == :file_server end diff --git a/spec/unit/application/cert_spec.rb b/spec/unit/application/cert_spec.rb index 25d74c859..f24ef8677 100755 --- a/spec/unit/application/cert_spec.rb +++ b/spec/unit/application/cert_spec.rb @@ -122,10 +122,12 @@ describe Puppet::Application::Cert => true do @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 - @ca.expects(:apply) + @iface.expects(:apply) @cert_app.main end @@ -133,7 +135,7 @@ describe Puppet::Application::Cert => true do it "should delegate with :all if option --all was given" do @cert_app.handle_all(0) - @ca.expects(:apply).with { |cert_mode,to| to[:to] == :all } + Puppet::SSL::CertificateAuthority::Interface.expects(:new).returns(@iface).with { |cert_mode,to| to[:to] == :all } @cert_app.main end @@ -141,7 +143,7 @@ describe Puppet::Application::Cert => true do it "should delegate to ca.apply with the hosts given on command line" do @cert_app.command_line.stubs(:args).returns(["host"]) - @ca.expects(:apply).with { |cert_mode,to| to[:to] == ["host"]} + Puppet::SSL::CertificateAuthority::Interface.expects(:new).returns(@iface).with { |cert_mode,to| to[:to] == ["host"]} @cert_app.main end @@ -150,7 +152,7 @@ describe Puppet::Application::Cert => true do @cert_app.command_line.stubs(:args).returns(["host"]) @cert_app.handle_digest(:digest) - @ca.expects(:apply).with { |cert_mode,to| to[:digest] == :digest} + Puppet::SSL::CertificateAuthority::Interface.expects(:new).returns(@iface).with { |cert_mode,to| to[:digest] == :digest} @cert_app.main end @@ -159,8 +161,8 @@ describe Puppet::Application::Cert => true do @cert_app.subcommand = :destroy @cert_app.command_line.stubs(:args).returns(["host"]) - @ca.expects(:apply).with { |cert_mode,to| cert_mode == :revoke } - @ca.expects(:apply).with { |cert_mode,to| cert_mode == :destroy } + 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 diff --git a/spec/unit/application/device_spec.rb b/spec/unit/application/device_spec.rb index 43787b69f..bbbc011c1 100755 --- a/spec/unit/application/device_spec.rb +++ b/spec/unit/application/device_spec.rb @@ -127,7 +127,7 @@ describe Puppet::Application::Device do before :each do @device.options.stubs(:[]) Puppet.stubs(:info) - FileTest.stubs(:exists?).returns(true) + Puppet::FileSystem::File.stubs(:exist?).returns(true) Puppet[:libdir] = "/dev/null/lib" Puppet::SSL::Host.stubs(:ca_location=) Puppet::Transaction::Report.indirection.stubs(:terminus_class=) diff --git a/spec/unit/application/filebucket_spec.rb b/spec/unit/application/filebucket_spec.rb index e7702e45a..4301c7dcd 100755 --- a/spec/unit/application/filebucket_spec.rb +++ b/spec/unit/application/filebucket_spec.rb @@ -178,7 +178,7 @@ describe Puppet::Application::Filebucket do it "should call the client backup method for each given parameter" do @filebucket.stubs(:puts) - FileTest.stubs(:exists?).returns(true) + Puppet::FileSystem::File.stubs(:exist?).returns(true) FileTest.stubs(:readable?).returns(true) @filebucket.stubs(:args).returns(["file1", "file2"]) diff --git a/spec/unit/application/inspect_spec.rb b/spec/unit/application/inspect_spec.rb index c0a034511..da73ee51c 100755 --- a/spec/unit/application/inspect_spec.rb +++ b/spec/unit/application/inspect_spec.rb @@ -36,7 +36,7 @@ describe Puppet::Application::Inspect do describe "when executing" do before :each do Puppet[:report] = true - @inspect.options[:logset] = true + @inspect.options[:setdest] = true Puppet::Transaction::Report::Rest.any_instance.stubs(:save) @inspect.setup end diff --git a/spec/unit/application_spec.rb b/spec/unit/application_spec.rb index 912416d53..1c9dd49b8 100755 --- a/spec/unit/application_spec.rb +++ b/spec/unit/application_spec.rb @@ -618,4 +618,28 @@ describe Puppet::Application do 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 + end + end + end diff --git a/spec/unit/configurer/downloader_spec.rb b/spec/unit/configurer/downloader_spec.rb index 403e92c1a..a818370fc 100755 --- a/spec/unit/configurer/downloader_spec.rb +++ b/spec/unit/configurer/downloader_spec.rb @@ -51,11 +51,7 @@ describe Puppet::Configurer::Downloader do @dler.file end - describe "on POSIX" do - before :each do - Puppet.features.stubs(:microsoft_windows?).returns false - end - + describe "on POSIX", :as_platform => :posix do it "should always set the owner to the current UID" do Process.expects(:uid).returns 51 Puppet::Type.type(:file).expects(:new).with { |opts| opts[:owner] == 51 } @@ -69,7 +65,7 @@ describe Puppet::Configurer::Downloader do end end - describe "on Windows", :if => Puppet.features.microsoft_windows? do + describe "on Windows", :as_platform => :windows do it "should omit the owner" do Puppet::Type.type(:file).expects(:new).with { |opts| opts[:owner] == nil } @dler.file @@ -79,6 +75,11 @@ describe Puppet::Configurer::Downloader do Puppet::Type.type(:file).expects(:new).with { |opts| opts[:group] == nil } @dler.file end + + it "should set source_permissions to ignore" do + Puppet::Type.type(:file).expects(:new).with { |opts| opts[:source_permissions] == :ignore } + @dler.file + end end it "should always force the download" do @@ -135,7 +136,7 @@ describe Puppet::Configurer::Downloader do Puppet[:tags] = 'maytag' @dler.evaluate - File.exists?(@dl_name).should be_true + Puppet::FileSystem::File.exist?(@dl_name).should be_true end it "should log that it is downloading" do diff --git a/spec/unit/configurer/fact_handler_spec.rb b/spec/unit/configurer/fact_handler_spec.rb index a74a91dfa..a92ad7d4f 100755 --- a/spec/unit/configurer/fact_handler_spec.rb +++ b/spec/unit/configurer/fact_handler_spec.rb @@ -3,6 +3,16 @@ require 'spec_helper' require 'puppet/configurer' require 'puppet/configurer/fact_handler' +# the json-schema gem doesn't support windows +if not Puppet.features.microsoft_windows? + describe "catalog facts schema" do + it "should validate against the json meta-schema" do + JSON::Validator.validate!(JSON_META_SCHEMA, FACTS_SCHEMA) + end + end + + end + class FactHandlerTester include Puppet::Configurer::FactHandler @@ -74,4 +84,17 @@ describe Puppet::Configurer::FactHandler do @facthandler.facts_for_uploading.should == {:facts_format => :pson, :facts => text} end + + def validate_json_for_facts(catalog_facts) + JSON::Validator.validate!(FACTS_SCHEMA, catalog_facts) + end + + it "should generate valid facts data against the facts schema", :unless => Puppet.features.microsoft_windows? do + facts = Puppet::Node::Facts.new(Puppet[:node_name_value], 'my_name_fact' => 'other_node_name') + Puppet::Node::Facts.indirection.save(facts) + + validate_json_for_facts(CGI.unescape(@facthandler.facts_for_uploading[:facts])) + end + end + diff --git a/spec/unit/configurer/plugin_handler_spec.rb b/spec/unit/configurer/plugin_handler_spec.rb index e5fda57e2..20027333d 100755 --- a/spec/unit/configurer/plugin_handler_spec.rb +++ b/spec/unit/configurer/plugin_handler_spec.rb @@ -19,7 +19,7 @@ describe Puppet::Configurer::PluginHandler do it "should use an Agent Downloader, with the name, source, destination, ignore, and environment set correctly, to download plugins when downloading is enabled" do downloader = mock 'downloader' - + Puppet.features.stubs(:external_facts?).returns(:true) # This is needed in order to make sure we pass on windows plugindest = File.expand_path("/tmp/pdest") @@ -27,9 +27,14 @@ describe Puppet::Configurer::PluginHandler do Puppet[:plugindest] = plugindest Puppet[:pluginsignore] = "pignore" + Puppet[:pluginfactsource] = "psource" + Puppet[:pluginfactdest] = plugindest + + Puppet::Configurer::Downloader.expects(:new).with("pluginfacts", plugindest, "psource", "pignore", "myenv").returns downloader Puppet::Configurer::Downloader.expects(:new).with("plugin", plugindest, "psource", "pignore", "myenv").returns downloader - downloader.expects(:evaluate).returns [] + downloader.stubs(:evaluate).returns([]) + downloader.expects(:evaluate).twice @pluginhandler.environment = "myenv" @pluginhandler.download_plugins diff --git a/spec/unit/configurer_spec.rb b/spec/unit/configurer_spec.rb index cba1df584..7732bea08 100755 --- a/spec/unit/configurer_spec.rb +++ b/spec/unit/configurer_spec.rb @@ -110,6 +110,16 @@ describe Puppet::Configurer do @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 @@ -341,12 +351,12 @@ describe Puppet::Configurer do @agent.environment.should == "second_env" end - it "should clear the thread local caches" do - Thread.current[:env_module_directories] = false + it "should clear the global caches" do + $env_module_directories = false @agent.run - Thread.current[:env_module_directories].should == nil + $env_module_directories.should == nil end describe "when not using a REST terminus for catalogs" do @@ -454,7 +464,7 @@ describe Puppet::Configurer do it "should write the last run file" do @configurer.save_last_run_summary(@report) - FileTest.exists?(Puppet[:lastrunfile]).should be_true + Puppet::FileSystem::File.exist?(Puppet[:lastrunfile]).should be_true end it "should write the raw summary as yaml" do @@ -486,7 +496,7 @@ describe Puppet::Configurer do require 'puppet/util/windows/security' mode = Puppet::Util::Windows::Security.get_mode(Puppet[:lastrunfile]) else - mode = File.stat(Puppet[:lastrunfile]).mode + mode = Puppet::FileSystem::File.new(Puppet[:lastrunfile]).stat.mode end (mode & 0777).should == 0664 end diff --git a/spec/unit/provider/confine/exists_spec.rb b/spec/unit/confine/exists_spec.rb index 17fd3f562..87959fe34 100755 --- a/spec/unit/provider/confine/exists_spec.rb +++ b/spec/unit/confine/exists_spec.rb @@ -1,20 +1,20 @@ #! /usr/bin/env ruby require 'spec_helper' -require 'puppet/provider/confine/exists' +require 'puppet/confine/exists' -describe Puppet::Provider::Confine::Exists do +describe Puppet::Confine::Exists do before do - @confine = Puppet::Provider::Confine::Exists.new("/my/file") + @confine = Puppet::Confine::Exists.new("/my/file") @confine.label = "eh" end it "should be named :exists" do - Puppet::Provider::Confine::Exists.name.should == :exists + Puppet::Confine::Exists.name.should == :exists end it "should not pass if exists is nil" do - confine = Puppet::Provider::Confine::Exists.new(nil) + confine = Puppet::Confine::Exists.new(nil) confine.label = ":exists => nil" confine.expects(:pass?).with(nil) confine.should_not be_valid @@ -30,12 +30,12 @@ describe Puppet::Provider::Confine::Exists do end it "should return false if the value does not point to a file" do - FileTest.expects(:exist?).with("/my/file").returns false + Puppet::FileSystem::File.expects(:exist?).with("/my/file").returns false @confine.pass?("/my/file").should be_false end it "should return true if the value points to a file" do - FileTest.expects(:exist?).with("/my/file").returns true + Puppet::FileSystem::File.expects(:exist?).with("/my/file").returns true @confine.pass?("/my/file").should be_true end @@ -62,11 +62,11 @@ describe Puppet::Provider::Confine::Exists do end it "should produce a summary containing all missing files" do - FileTest.stubs(:exist?).returns true - FileTest.expects(:exist?).with("/two").returns false - FileTest.expects(:exist?).with("/four").returns false + Puppet::FileSystem::File.stubs(:exist?).returns true + Puppet::FileSystem::File.expects(:exist?).with("/two").returns false + Puppet::FileSystem::File.expects(:exist?).with("/four").returns false - confine = Puppet::Provider::Confine::Exists.new %w{/one /two /three /four} + confine = Puppet::Confine::Exists.new %w{/one /two /three /four} confine.summary.should == %w{/two /four} end @@ -75,6 +75,6 @@ describe Puppet::Provider::Confine::Exists do c2 = mock '2', :summary => %w{two} c3 = mock '3', :summary => %w{three} - Puppet::Provider::Confine::Exists.summarize([c1, c2, c3]).should == %w{one two three} + Puppet::Confine::Exists.summarize([c1, c2, c3]).should == %w{one two three} end end diff --git a/spec/unit/provider/confine/false_spec.rb b/spec/unit/confine/false_spec.rb index 91d738c52..44ecd40ed 100755 --- a/spec/unit/provider/confine/false_spec.rb +++ b/spec/unit/confine/false_spec.rb @@ -1,22 +1,22 @@ #! /usr/bin/env ruby require 'spec_helper' -require 'puppet/provider/confine/false' +require 'puppet/confine/false' -describe Puppet::Provider::Confine::False do +describe Puppet::Confine::False do it "should be named :false" do - Puppet::Provider::Confine::False.name.should == :false + Puppet::Confine::False.name.should == :false end it "should require a value" do - lambda { Puppet::Provider::Confine.new }.should raise_error(ArgumentError) + lambda { Puppet::Confine.new }.should raise_error(ArgumentError) end describe "when testing values" do - before { @confine = Puppet::Provider::Confine::False.new("foo") } + before { @confine = Puppet::Confine::False.new("foo") } it "should use the 'pass?' method to test validity" do - @confine = Puppet::Provider::Confine::False.new("foo") + @confine = Puppet::Confine::False.new("foo") @confine.label = "eh" @confine.expects(:pass?).with("foo") @confine.valid? @@ -31,13 +31,13 @@ describe Puppet::Provider::Confine::False do end it "should produce a message that a value is true" do - @confine = Puppet::Provider::Confine::False.new("foo") + @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::Provider::Confine::False.new %w{one two three four} + 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 @@ -47,6 +47,6 @@ describe Puppet::Provider::Confine::False do c2 = mock '2', :summary => 2 c3 = mock '3', :summary => 3 - Puppet::Provider::Confine::False.summarize([c1, c2, c3]).should == 6 + Puppet::Confine::False.summarize([c1, c2, c3]).should == 6 end end diff --git a/spec/unit/provider/confine/feature_spec.rb b/spec/unit/confine/feature_spec.rb index b90662235..dad9d9b8b 100755 --- a/spec/unit/provider/confine/feature_spec.rb +++ b/spec/unit/confine/feature_spec.rb @@ -1,24 +1,24 @@ #! /usr/bin/env ruby require 'spec_helper' -require 'puppet/provider/confine/feature' +require 'puppet/confine/feature' -describe Puppet::Provider::Confine::Feature do +describe Puppet::Confine::Feature do it "should be named :feature" do - Puppet::Provider::Confine::Feature.name.should == :feature + Puppet::Confine::Feature.name.should == :feature end it "should require a value" do - lambda { Puppet::Provider::Confine::Feature.new }.should raise_error(ArgumentError) + lambda { Puppet::Confine::Feature.new }.should raise_error(ArgumentError) end it "should always convert values to an array" do - Puppet::Provider::Confine::Feature.new("/some/file").values.should be_instance_of(Array) + Puppet::Confine::Feature.new("/some/file").values.should be_instance_of(Array) end describe "when testing values" do before do - @confine = Puppet::Provider::Confine::Feature.new("myfeature") + @confine = Puppet::Confine::Feature.new("myfeature") @confine.label = "eh" end @@ -44,14 +44,14 @@ describe Puppet::Provider::Confine::Feature do it "should summarize multiple instances by returning a flattened array of all missing features" do confines = [] - confines << Puppet::Provider::Confine::Feature.new(%w{one two}) - confines << Puppet::Provider::Confine::Feature.new(%w{two}) - confines << Puppet::Provider::Confine::Feature.new(%w{three four}) + 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::Provider::Confine::Feature.summarize(confines).sort.should == %w{one two three four}.sort + Puppet::Confine::Feature.summarize(confines).sort.should == %w{one two three four}.sort end end diff --git a/spec/unit/provider/confine/true_spec.rb b/spec/unit/confine/true_spec.rb index e869817f0..d7484d59c 100755 --- a/spec/unit/provider/confine/true_spec.rb +++ b/spec/unit/confine/true_spec.rb @@ -1,20 +1,20 @@ #! /usr/bin/env ruby require 'spec_helper' -require 'puppet/provider/confine/true' +require 'puppet/confine/true' -describe Puppet::Provider::Confine::True do +describe Puppet::Confine::True do it "should be named :true" do - Puppet::Provider::Confine::True.name.should == :true + Puppet::Confine::True.name.should == :true end it "should require a value" do - lambda { Puppet::Provider::Confine::True.new }.should raise_error(ArgumentError) + lambda { Puppet::Confine::True.new }.should raise_error(ArgumentError) end describe "when testing values" do before do - @confine = Puppet::Provider::Confine::True.new("foo") + @confine = Puppet::Confine::True.new("foo") @confine.label = "eh" end @@ -37,7 +37,7 @@ describe Puppet::Provider::Confine::True do end it "should produce the number of false values when asked for a summary" do - @confine = Puppet::Provider::Confine::True.new %w{one two three four} + @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 @@ -47,6 +47,6 @@ describe Puppet::Provider::Confine::True do c2 = mock '2', :summary => 2 c3 = mock '3', :summary => 3 - Puppet::Provider::Confine::True.summarize([c1, c2, c3]).should == 6 + Puppet::Confine::True.summarize([c1, c2, c3]).should == 6 end end diff --git a/spec/unit/provider/confine/variable_spec.rb b/spec/unit/confine/variable_spec.rb index d06705c6f..be45e3bb1 100755 --- a/spec/unit/provider/confine/variable_spec.rb +++ b/spec/unit/confine/variable_spec.rb @@ -1,28 +1,28 @@ #! /usr/bin/env ruby require 'spec_helper' -require 'puppet/provider/confine/variable' +require 'puppet/confine/variable' -describe Puppet::Provider::Confine::Variable do +describe Puppet::Confine::Variable do it "should be named :variable" do - Puppet::Provider::Confine::Variable.name.should == :variable + Puppet::Confine::Variable.name.should == :variable end it "should require a value" do - lambda { Puppet::Provider::Confine::Variable.new }.should raise_error(ArgumentError) + lambda { Puppet::Confine::Variable.new }.should raise_error(ArgumentError) end it "should always convert values to an array" do - Puppet::Provider::Confine::Variable.new("/some/file").values.should be_instance_of(Array) + Puppet::Confine::Variable.new("/some/file").values.should be_instance_of(Array) end it "should have an accessor for its name" do - Puppet::Provider::Confine::Variable.new(:bar).should respond_to(:name) + Puppet::Confine::Variable.new(:bar).should respond_to(:name) end describe "when testing values" do before do - @confine = Puppet::Provider::Confine::Variable.new("foo") + @confine = Puppet::Confine::Variable.new("foo") @confine.name = :myvar end @@ -63,7 +63,7 @@ describe Puppet::Provider::Confine::Variable do end it "should produce a message that the fact value is not correct" do - @confine = Puppet::Provider::Confine::Variable.new(%w{bar bee}) + @confine = Puppet::Confine::Variable.new(%w{bar bee}) @confine.name = "eh" message = @confine.message("value") message.should be_include("facter") @@ -71,7 +71,7 @@ describe Puppet::Provider::Confine::Variable do end it "should be valid if the test value matches any of the provided values" do - @confine = Puppet::Provider::Confine::Variable.new(%w{bar bee}) + @confine = Puppet::Confine::Variable.new(%w{bar bee}) @confine.expects(:test_value).returns "bee" @confine.should be_valid end @@ -79,28 +79,28 @@ describe Puppet::Provider::Confine::Variable do describe "when summarizing multiple instances" do it "should return a hash of failing variables and their values" do - c1 = Puppet::Provider::Confine::Variable.new("one") + c1 = Puppet::Confine::Variable.new("one") c1.name = "uno" c1.expects(:valid?).returns false - c2 = Puppet::Provider::Confine::Variable.new("two") + c2 = Puppet::Confine::Variable.new("two") c2.name = "dos" c2.expects(:valid?).returns true - c3 = Puppet::Provider::Confine::Variable.new("three") + c3 = Puppet::Confine::Variable.new("three") c3.name = "tres" c3.expects(:valid?).returns false - Puppet::Provider::Confine::Variable.summarize([c1, c2, c3]).should == {"uno" => %w{one}, "tres" => %w{three}} + Puppet::Confine::Variable.summarize([c1, c2, c3]).should == {"uno" => %w{one}, "tres" => %w{three}} end it "should combine the values of multiple confines with the same fact" do - c1 = Puppet::Provider::Confine::Variable.new("one") + c1 = Puppet::Confine::Variable.new("one") c1.name = "uno" c1.expects(:valid?).returns false - c2 = Puppet::Provider::Confine::Variable.new("two") + c2 = Puppet::Confine::Variable.new("two") c2.name = "uno" c2.expects(:valid?).returns false - Puppet::Provider::Confine::Variable.summarize([c1, c2]).should == {"uno" => %w{one two}} + Puppet::Confine::Variable.summarize([c1, c2]).should == {"uno" => %w{one two}} end end end diff --git a/spec/unit/provider/confine_collection_spec.rb b/spec/unit/confine_collection_spec.rb index 31a778fd7..958f69197 100755 --- a/spec/unit/provider/confine_collection_spec.rb +++ b/spec/unit/confine_collection_spec.rb @@ -1,72 +1,72 @@ #! /usr/bin/env ruby require 'spec_helper' -require 'puppet/provider/confine_collection' +require 'puppet/confine_collection' -describe Puppet::Provider::ConfineCollection do +describe Puppet::ConfineCollection do it "should be able to add confines" do - Puppet::Provider::ConfineCollection.new("label").should respond_to(:confine) + Puppet::ConfineCollection.new("label").should respond_to(:confine) end it "should require a label at initialization" do - lambda { Puppet::Provider::ConfineCollection.new }.should raise_error(ArgumentError) + lambda { Puppet::ConfineCollection.new }.should raise_error(ArgumentError) end it "should make its label available" do - Puppet::Provider::ConfineCollection.new("mylabel").label.should == "mylabel" + Puppet::ConfineCollection.new("mylabel").label.should == "mylabel" end describe "when creating confine instances" do it "should create an instance of the named test with the provided values" do test_class = mock 'test_class' test_class.expects(:new).with(%w{my values}).returns(stub('confine', :label= => nil)) - Puppet::Provider::Confine.expects(:test).with(:foo).returns test_class + Puppet::Confine.expects(:test).with(:foo).returns test_class - Puppet::Provider::ConfineCollection.new("label").confine :foo => %w{my values} + Puppet::ConfineCollection.new("label").confine :foo => %w{my values} end it "should copy its label to the confine instance" do confine = mock 'confine' test_class = mock 'test_class' test_class.expects(:new).returns confine - Puppet::Provider::Confine.expects(:test).returns test_class + Puppet::Confine.expects(:test).returns test_class confine.expects(:label=).with("label") - Puppet::Provider::ConfineCollection.new("label").confine :foo => %w{my values} + Puppet::ConfineCollection.new("label").confine :foo => %w{my values} end describe "and the test cannot be found" do it "should create a Facter test with the provided values and set the name to the test name" do - confine = Puppet::Provider::Confine.test(:variable).new(%w{my values}) + confine = Puppet::Confine.test(:variable).new(%w{my values}) confine.expects(:name=).with(:foo) confine.class.expects(:new).with(%w{my values}).returns confine - Puppet::Provider::ConfineCollection.new("label").confine(:foo => %w{my values}) + Puppet::ConfineCollection.new("label").confine(:foo => %w{my values}) end end describe "and the 'for_binary' option was provided" do it "should mark the test as a binary confine" do - confine = Puppet::Provider::Confine.test(:exists).new(:bar) + confine = Puppet::Confine.test(:exists).new(:bar) confine.expects(:for_binary=).with true - Puppet::Provider::Confine.test(:exists).expects(:new).with(:bar).returns confine - Puppet::Provider::ConfineCollection.new("label").confine :exists => :bar, :for_binary => true + Puppet::Confine.test(:exists).expects(:new).with(:bar).returns confine + Puppet::ConfineCollection.new("label").confine :exists => :bar, :for_binary => true end end end it "should be valid if no confines are present" do - Puppet::Provider::ConfineCollection.new("label").should be_valid + Puppet::ConfineCollection.new("label").should be_valid end it "should be valid if all confines pass" do c1 = stub 'c1', :valid? => true, :label= => nil c2 = stub 'c2', :valid? => true, :label= => nil - Puppet::Provider::Confine.test(:true).expects(:new).returns(c1) - Puppet::Provider::Confine.test(:false).expects(:new).returns(c2) + Puppet::Confine.test(:true).expects(:new).returns(c1) + Puppet::Confine.test(:false).expects(:new).returns(c2) - confiner = Puppet::Provider::ConfineCollection.new("label") + confiner = Puppet::ConfineCollection.new("label") confiner.confine :true => :bar, :false => :bee confiner.should be_valid @@ -76,10 +76,10 @@ describe Puppet::Provider::ConfineCollection do c1 = stub 'c1', :valid? => true, :label= => nil c2 = stub 'c2', :valid? => false, :label= => nil - Puppet::Provider::Confine.test(:true).expects(:new).returns(c1) - Puppet::Provider::Confine.test(:false).expects(:new).returns(c2) + Puppet::Confine.test(:true).expects(:new).returns(c1) + Puppet::Confine.test(:false).expects(:new).returns(c2) - confiner = Puppet::Provider::ConfineCollection.new("label") + confiner = Puppet::ConfineCollection.new("label") confiner.confine :true => :bar, :false => :bee confiner.should_not be_valid @@ -87,7 +87,7 @@ describe Puppet::Provider::ConfineCollection do describe "when providing a summary" do before do - @confiner = Puppet::Provider::ConfineCollection.new("label") + @confiner = Puppet::ConfineCollection.new("label") end it "should return a hash" do @@ -100,32 +100,32 @@ describe Puppet::Provider::ConfineCollection do it "should add each test type's summary to the hash" do @confiner.confine :true => :bar, :false => :bee - Puppet::Provider::Confine.test(:true).expects(:summarize).returns :tsumm - Puppet::Provider::Confine.test(:false).expects(:summarize).returns :fsumm + Puppet::Confine.test(:true).expects(:summarize).returns :tsumm + Puppet::Confine.test(:false).expects(:summarize).returns :fsumm @confiner.summary.should == {:true => :tsumm, :false => :fsumm} end it "should not include tests that return 0" do @confiner.confine :true => :bar, :false => :bee - Puppet::Provider::Confine.test(:true).expects(:summarize).returns 0 - Puppet::Provider::Confine.test(:false).expects(:summarize).returns :fsumm + Puppet::Confine.test(:true).expects(:summarize).returns 0 + Puppet::Confine.test(:false).expects(:summarize).returns :fsumm @confiner.summary.should == {:false => :fsumm} end it "should not include tests that return empty arrays" do @confiner.confine :true => :bar, :false => :bee - Puppet::Provider::Confine.test(:true).expects(:summarize).returns [] - Puppet::Provider::Confine.test(:false).expects(:summarize).returns :fsumm + Puppet::Confine.test(:true).expects(:summarize).returns [] + Puppet::Confine.test(:false).expects(:summarize).returns :fsumm @confiner.summary.should == {:false => :fsumm} end it "should not include tests that return empty hashes" do @confiner.confine :true => :bar, :false => :bee - Puppet::Provider::Confine.test(:true).expects(:summarize).returns({}) - Puppet::Provider::Confine.test(:false).expects(:summarize).returns :fsumm + Puppet::Confine.test(:true).expects(:summarize).returns({}) + Puppet::Confine.test(:false).expects(:summarize).returns :fsumm @confiner.summary.should == {:false => :fsumm} end diff --git a/spec/unit/provider/confine_spec.rb b/spec/unit/confine_spec.rb index 38dfd8c3f..06c10abd7 100755 --- a/spec/unit/provider/confine_spec.rb +++ b/spec/unit/confine_spec.rb @@ -1,40 +1,40 @@ #! /usr/bin/env ruby require 'spec_helper' -require 'puppet/provider/confine' +require 'puppet/confine' -describe Puppet::Provider::Confine do +describe Puppet::Confine do it "should require a value" do - lambda { Puppet::Provider::Confine.new }.should raise_error(ArgumentError) + lambda { Puppet::Confine.new }.should raise_error(ArgumentError) end it "should always convert values to an array" do - Puppet::Provider::Confine.new("/some/file").values.should be_instance_of(Array) + Puppet::Confine.new("/some/file").values.should be_instance_of(Array) end it "should have a 'true' test" do - Puppet::Provider::Confine.test(:true).should be_instance_of(Class) + Puppet::Confine.test(:true).should be_instance_of(Class) end it "should have a 'false' test" do - Puppet::Provider::Confine.test(:false).should be_instance_of(Class) + Puppet::Confine.test(:false).should be_instance_of(Class) end it "should have a 'feature' test" do - Puppet::Provider::Confine.test(:feature).should be_instance_of(Class) + Puppet::Confine.test(:feature).should be_instance_of(Class) end it "should have an 'exists' test" do - Puppet::Provider::Confine.test(:exists).should be_instance_of(Class) + Puppet::Confine.test(:exists).should be_instance_of(Class) end it "should have a 'variable' test" do - Puppet::Provider::Confine.test(:variable).should be_instance_of(Class) + Puppet::Confine.test(:variable).should be_instance_of(Class) end describe "when testing all values" do before do - @confine = Puppet::Provider::Confine.new(%w{a b c}) + @confine = Puppet::Confine.new(%w{a b c}) @confine.label = "foo" end @@ -64,7 +64,7 @@ describe Puppet::Provider::Confine do end describe "when testing the result of the values" do - before { @confine = Puppet::Provider::Confine.new(%w{a b c d}) } + before { @confine = Puppet::Confine.new(%w{a b c d}) } it "should return an array with the result of the test for each value" do @confine.stubs(:pass?).returns true diff --git a/spec/unit/provider/confiner_spec.rb b/spec/unit/confiner_spec.rb index 2afdd71a3..2d71054fe 100755 --- a/spec/unit/provider/confiner_spec.rb +++ b/spec/unit/confiner_spec.rb @@ -1,12 +1,12 @@ #! /usr/bin/env ruby require 'spec_helper' -require 'puppet/provider/confiner' +require 'puppet/confiner' -describe Puppet::Provider::Confiner do +describe Puppet::Confiner do before do @object = Object.new - @object.extend(Puppet::Provider::Confiner) + @object.extend(Puppet::Confiner) end it "should have a method for defining confines" do @@ -29,7 +29,7 @@ describe Puppet::Provider::Confiner do end it "should create a new confine collection if one does not exist" do - Puppet::Provider::ConfineCollection.expects(:new).with("mylabel").returns "mycoll" + Puppet::ConfineCollection.expects(:new).with("mylabel").returns "mycoll" @object.expects(:to_s).returns "mylabel" @object.confine_collection.should == "mycoll" end diff --git a/spec/unit/face/parser_spec.rb b/spec/unit/face/parser_spec.rb new file mode 100644 index 000000000..a517ae641 --- /dev/null +++ b/spec/unit/face/parser_spec.rb @@ -0,0 +1,54 @@ +require 'spec_helper' +require 'puppet_spec/files' + +require 'puppet/face' + +describe Puppet::Face[:parser, :current] do + include PuppetSpec::Files + + let(:parser) { Puppet::Face[:parser, :current] } + + it "validates the configured site manifest when no files are given" do + Puppet[:manifest] = file_containing('site.pp', "{ invalid =>") + from_an_interactive_terminal + + expect { parser.validate() }.to exit_with(1) + end + + it "validates the given file" do + manifest = file_containing('site.pp', "{ invalid =>") + from_an_interactive_terminal + + expect { parser.validate(manifest) }.to exit_with(1) + end + + it "validates the contents of STDIN when no files given and STDIN is not a tty" do + from_a_piped_input_of("{ invalid =>") + + expect { parser.validate() }.to exit_with(1) + end + + it "runs error free when there are no validation errors" do + manifest = file_containing('site.pp', "notify { valid: }") + from_an_interactive_terminal + + parser.validate(manifest) + end + + it "reports missing files" do + from_an_interactive_terminal + + expect do + parser.validate("missing.pp") + end.to raise_error(Puppet::Error, /One or more file\(s\) specified did not exist.*missing\.pp/m) + end + + def from_an_interactive_terminal + STDIN.stubs(:tty?).returns(true) + end + + def from_a_piped_input_of(contents) + STDIN.stubs(:tty?).returns(false) + STDIN.stubs(:read).returns(contents) + end +end diff --git a/spec/unit/file_bucket/dipper_spec.rb b/spec/unit/file_bucket/dipper_spec.rb index 397cb9219..83e914851 100755 --- a/spec/unit/file_bucket/dipper_spec.rb +++ b/spec/unit/file_bucket/dipper_spec.rb @@ -45,7 +45,7 @@ describe Puppet::FileBucket::Dipper do Digest::MD5.hexdigest("my\r\ncontents").should == checksum @dipper.backup(file).should == checksum - File.exists?("#{file_bucket}/f/0/d/7/d/4/e/4/f0d7d4e480ad698ed56aeec8b6bd6dea/contents").should == true + Puppet::FileSystem::File.exist?("#{file_bucket}/f/0/d/7/d/4/e/4/f0d7d4e480ad698ed56aeec8b6bd6dea/contents").should == true end it "should not backup a file that is already in the bucket" do @@ -123,7 +123,7 @@ describe Puppet::FileBucket::Dipper do klass.any_instance.expects(:find).with { |r| request = r }.returns(Puppet::FileBucket::File.new(contents)) dipper.restore(dest, md5).should == md5 - Digest::MD5.hexdigest(IO.binread(dest)).should == md5 + Digest::MD5.hexdigest(Puppet::FileSystem::File.new(dest).binread).should == md5 request.key.should == "md5/#{md5}" request.server.should == server diff --git a/spec/unit/file_serving/base_spec.rb b/spec/unit/file_serving/base_spec.rb index a172830e8..65168d3a3 100755 --- a/spec/unit/file_serving/base_spec.rb +++ b/spec/unit/file_serving/base_spec.rb @@ -42,12 +42,12 @@ describe Puppet::FileServing::Base do end it "should allow specification of a path" do - FileTest.stubs(:exists?).returns(true) + Puppet::FileSystem::File.stubs(:exist?).returns(true) Puppet::FileServing::Base.new(path, :path => file).path.should == file end it "should allow specification of a relative path" do - FileTest.stubs(:exists?).returns(true) + Puppet::FileSystem::File.stubs(:exist?).returns(true) Puppet::FileServing::Base.new(path, :relative_path => "my/file").relative_path.should == "my/file" end @@ -56,19 +56,22 @@ describe Puppet::FileServing::Base do end it "should correctly indicate if the file is present" do - File.expects(:lstat).with(file).returns(mock("stat")) + mock_file = mock(file, :lstat => stub('stat')) + Puppet::FileSystem::File.expects(:new).with(file).returns mock_file Puppet::FileServing::Base.new(file).exist?.should be_true end it "should correctly indicate if the file is absent" do - File.expects(:lstat).with(file).raises RuntimeError + mock_file = mock(file) + Puppet::FileSystem::File.expects(:new).with(file).returns mock_file + mock_file.expects(:lstat).raises RuntimeError Puppet::FileServing::Base.new(file).exist?.should be_false end describe "when setting the relative path" do it "should require that the relative path be unqualified" do @file = Puppet::FileServing::Base.new(path) - FileTest.stubs(:exists?).returns(true) + Puppet::FileSystem::File.stubs(:exist?).returns(true) proc { @file.relative_path = File.expand_path("/qualified/file") }.should raise_error(ArgumentError) end end @@ -102,27 +105,47 @@ describe Puppet::FileServing::Base do 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 - File.expects(:lstat).with(path).returns stub("stat", :ftype => "file") + Puppet::FileSystem::File.expects(:new).with(path).returns stubbed_file file.stat end it "should fail if the file does not exist" do - File.expects(:lstat).with(path).raises(Errno::ENOENT) + Puppet::FileSystem::File.expects(:new).with(path).returns stubbed_file + stubbed_file.expects(:lstat).raises(Errno::ENOENT) proc { file.stat }.should raise_error(Errno::ENOENT) end it "should use :lstat if :links is set to :manage" do - File.expects(:lstat).with(path).returns stub("stat", :ftype => "file") + Puppet::FileSystem::File.expects(:new).with(path).returns stubbed_file file.stat end it "should use :stat if :links is set to :follow" do - File.expects(:stat).with(path).returns stub("stat", :ftype => "file") + Puppet::FileSystem::File.expects(:new).with(path).returns stubbed_file file.links = :follow file.stat end diff --git a/spec/unit/file_serving/configuration_spec.rb b/spec/unit/file_serving/configuration_spec.rb index 2552cd808..b6999e086 100755 --- a/spec/unit/file_serving/configuration_spec.rb +++ b/spec/unit/file_serving/configuration_spec.rb @@ -27,12 +27,12 @@ describe Puppet::FileServing::Configuration do describe "when initializing" do it "should work without a configuration file" do - FileTest.stubs(:exists?).with(@path).returns(false) + Puppet::FileSystem::File.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 - FileTest.stubs(:exists?).with(@path).returns(true) + Puppet::FileSystem::File.stubs(:exist?).with(@path).returns(true) @parser = mock 'parser' @parser.expects(:parse).returns({}) Puppet::FileServing::Configuration::Parser.stubs(:new).returns(@parser) @@ -47,7 +47,7 @@ describe Puppet::FileServing::Configuration do describe "when parsing the configuration file" do before do - FileTest.stubs(:exists?).with(@path).returns(true) + Puppet::FileSystem::File.stubs(:exist?).with(@path).returns(true) @parser = mock 'parser' Puppet::FileServing::Configuration::Parser.stubs(:new).returns(@parser) end @@ -84,14 +84,14 @@ describe Puppet::FileServing::Configuration do end it "should add modules and plugins mounts even if the file does not exist" do - FileTest.expects(:exists?).returns false # the file doesn't exist + Puppet::FileSystem::File.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 end it "should allow all access to modules and plugins if no fileserver.conf exists" do - FileTest.expects(:exists?).returns false # the file doesn't exist + Puppet::FileSystem::File.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('*') @@ -104,7 +104,7 @@ describe Puppet::FileServing::Configuration do end it "should not allow access from all to modules and plugins if the fileserver.conf provided some rules" do - FileTest.expects(:exists?).returns false # the file doesn't exist + Puppet::FileSystem::File.expects(:exist?).returns false # the file doesn't exist modules = stub 'modules', :empty? => false Puppet::FileServing::Mount::Modules.stubs(:new).returns(modules) @@ -119,7 +119,7 @@ describe Puppet::FileServing::Configuration do it "should add modules and plugins mounts even if they are not returned by the parser" do @parser.expects(:parse).returns("one" => mock("mount")) - FileTest.expects(:exists?).returns true # the file doesn't exist + Puppet::FileSystem::File.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 diff --git a/spec/unit/file_serving/content_spec.rb b/spec/unit/file_serving/content_spec.rb index 2169c9b57..2cb159f0a 100755 --- a/spec/unit/file_serving/content_spec.rb +++ b/spec/unit/file_serving/content_spec.rb @@ -26,7 +26,8 @@ describe Puppet::FileServing::Content do content = Puppet::FileServing::Content.new(path) result = "foo" - File.stubs(:lstat).returns(stub("stat", :ftype => "file")) + stub_file = stub(path, :lstat => stub('stat', :ftype => "file")) + Puppet::FileSystem::File.expects(:new).with(path).returns stub_file File.expects(:read).with(path).never content.collect @@ -37,7 +38,8 @@ describe Puppet::FileServing::Content do content = Puppet::FileServing::Content.new(path) result = "foo" - File.stubs(:lstat).returns(stub("stat", :ftype => "directory")) + stub_file = stub(path, :lstat => stub('stat', :ftype => "directory")) + Puppet::FileSystem::File.expects(:new).with(path).returns stub_file File.expects(:read).with(path).never content.collect @@ -83,7 +85,8 @@ describe Puppet::FileServing::Content, "when returning the contents" do it "should fail if the file is a symlink and links are set to :manage" do content.links = :manage - File.expects(:lstat).with(path).returns stub("stat", :ftype => "symlink") + stub_file = stub(path, :lstat => stub("stat", :ftype => "symlink")) + Puppet::FileSystem::File.expects(:new).with(path).returns stub_file proc { content.content }.should raise_error(ArgumentError) end @@ -97,14 +100,16 @@ describe Puppet::FileServing::Content, "when returning the contents" do end it "should return the contents of the path if the file exists" do - File.expects(:stat).with(path).returns stub("stat", :ftype => "file") - IO.expects(:binread).with(path).returns(:mycontent) + mocked_file = mock(path, :stat => stub('stat', :ftype => 'file')) + Puppet::FileSystem::File.expects(:new).with(path).twice.returns(mocked_file) + mocked_file.expects(:binread).returns(:mycontent) content.content.should == :mycontent end it "should cache the returned contents" do - File.expects(:stat).with(path).returns stub("stat", :ftype => "file") - IO.expects(:binread).with(path).returns(:mycontent) + mocked_file = mock(path, :stat => stub('stat', :ftype => 'file')) + Puppet::FileSystem::File.expects(:new).with(path).twice.returns(mocked_file) + mocked_file.expects(:binread).returns(:mycontent) content.content # The second run would throw a failure if the content weren't being cached. content.content diff --git a/spec/unit/file_serving/fileset_spec.rb b/spec/unit/file_serving/fileset_spec.rb index f6a661843..0e61a2a59 100755 --- a/spec/unit/file_serving/fileset_spec.rb +++ b/spec/unit/file_serving/fileset_spec.rb @@ -18,49 +18,58 @@ describe Puppet::FileServing::Fileset do it "removes a trailing file path separator" do path_with_separator = "#{somefile}#{File::SEPARATOR}" - File.stubs(:lstat).with(somefile).returns stub('stat') + stub_file = stub(somefile, :lstat => stub('stat')) + Puppet::FileSystem::File.expects(:new).with(somefile).returns stub_file 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) - File.stubs(:lstat).with(path).returns stub('stat') + stub_file = stub(path, :lstat => stub('stat')) + Puppet::FileSystem::File.expects(:new).with(path).returns stub_file fileset = Puppet::FileServing::Fileset.new(path) fileset.path.should == path end it "fails if its path does not exist" do - File.expects(:lstat).with(somefile).raises(Errno::ENOENT) + mock_file = mock(somefile) + Puppet::FileSystem::File.expects(:new).with(somefile).returns mock_file + mock_file.expects(:lstat).raises(Errno::ENOENT) expect { Puppet::FileServing::Fileset.new(somefile) }.to raise_error(ArgumentError, "Fileset paths must exist") end it "accepts a 'recurse' option" do - File.expects(:lstat).with(somefile).returns stub("stat") + stub_file = stub(somefile, :lstat => stub('stat')) + Puppet::FileSystem::File.expects(:new).with(somefile).returns stub_file set = Puppet::FileServing::Fileset.new(somefile, :recurse => true) set.recurse.should be_true end it "accepts a 'recurselimit' option" do - File.expects(:lstat).with(somefile).returns stub("stat") + stub_file = stub(somefile, :lstat => stub('stat')) + Puppet::FileSystem::File.expects(:new).with(somefile).returns stub_file set = Puppet::FileServing::Fileset.new(somefile, :recurselimit => 3) set.recurselimit.should == 3 end it "accepts an 'ignore' option" do - File.expects(:lstat).with(somefile).returns stub("stat") + stub_file = stub(somefile, :lstat => stub('stat')) + Puppet::FileSystem::File.expects(:new).with(somefile).returns stub_file set = Puppet::FileServing::Fileset.new(somefile, :ignore => ".svn") set.ignore.should == [".svn"] end it "accepts a 'links' option" do - File.expects(:lstat).with(somefile).returns stub("stat") + stub_file = stub(somefile, :lstat => stub('stat')) + Puppet::FileSystem::File.expects(:new).with(somefile).returns stub_file set = Puppet::FileServing::Fileset.new(somefile, :links => :manage) set.links.should == :manage end it "accepts a 'checksum_type' option" do - File.expects(:lstat).with(somefile).returns stub("stat") + stub_file = stub(somefile, :lstat => stub('stat')) + Puppet::FileSystem::File.expects(:new).with(somefile).returns stub_file set = Puppet::FileServing::Fileset.new(somefile, :checksum_type => :test) set.checksum_type.should == :test end @@ -70,30 +79,35 @@ describe Puppet::FileServing::Fileset do end it "defaults to 'false' for recurse" do - File.expects(:lstat).with(somefile).returns stub("stat") + stub_file = stub(somefile, :lstat => stub('stat')) + Puppet::FileSystem::File.expects(:new).with(somefile).returns stub_file Puppet::FileServing::Fileset.new(somefile).recurse.should == false end it "defaults to :infinite for recurselimit" do - File.expects(:lstat).with(somefile).returns stub("stat") + stub_file = stub(somefile, :lstat => stub('stat')) + Puppet::FileSystem::File.expects(:new).with(somefile).returns stub_file Puppet::FileServing::Fileset.new(somefile).recurselimit.should == :infinite end it "defaults to an empty ignore list" do - File.expects(:lstat).with(somefile).returns stub("stat") + stub_file = stub(somefile, :lstat => stub('stat')) + Puppet::FileSystem::File.expects(:new).with(somefile).returns stub_file Puppet::FileServing::Fileset.new(somefile).ignore.should == [] end it "defaults to :manage for links" do - File.expects(:lstat).with(somefile).returns stub("stat") + stub_file = stub(somefile, :lstat => stub('stat')) + Puppet::FileSystem::File.expects(:new).with(somefile).returns stub_file 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 - File.stubs(:lstat).returns stub("stat") + Puppet::FileSystem::File.expects(:new).with(somefile).returns stub_file end [:recurse, :recurselimit, :ignore, :links].each do |option| @@ -130,7 +144,8 @@ describe Puppet::FileServing::Fileset do context "when recursing" do before do @path = make_absolute("/my/path") - File.expects(:lstat).with(@path).returns stub("stat", :directory? => true) + @stub_file = stub(@path, :lstat => stub('stat', :directory? => true)) + Puppet::FileSystem::File.stubs(:new).with(@path).returns @stub_file @fileset = Puppet::FileServing::Fileset.new(@path) @dirstat = stub 'dirstat', :directory? => true @@ -138,7 +153,7 @@ describe Puppet::FileServing::Fileset do end def mock_dir_structure(path, stat_method = :lstat) - File.stubs(stat_method).with(path).returns(@dirstat) + @stub_file.stubs(stat_method).returns(@dirstat) Dir.stubs(:entries).with(path).returns(%w{one two .svn CVS}) # Keep track of the files we're stubbing. @@ -147,11 +162,14 @@ describe Puppet::FileServing::Fileset do %w{one two .svn CVS}.each do |subdir| @files << subdir # relative path subpath = File.join(path, subdir) - File.stubs(stat_method).with(subpath).returns(@dirstat) + stub_subpath = stub(subpath, stat_method => @dirstat) + Puppet::FileSystem::File.stubs(:new).with(subpath).returns stub_subpath Dir.stubs(:entries).with(subpath).returns(%w{.svn CVS file1 file2}) %w{file1 file2 .svn CVS}.each do |file| @files << File.join(subdir, file) # relative path - File.stubs(stat_method).with(File.join(subpath, file)).returns(@filestat) + subfile_path = File.join(subpath, file) + stub_subfile_path = stub(subfile_path, stat_method => @filestat) + Puppet::FileSystem::File.stubs(:new).with(subfile_path).returns stub_subfile_path end end end @@ -165,8 +183,10 @@ describe Puppet::FileServing::Fileset do MockDirectory = Struct.new(:name, :entries) do def mock(base_path) + extend Mocha::API path = File.join(base_path, name) - File.stubs(:lstat).with(path).returns(MockStat.new(path, true)) + stub_dir = stub(path, :lstat => MockStat.new(path, true)) + Puppet::FileSystem::File.stubs(:new).with(path).returns stub_dir Dir.stubs(:entries).with(path).returns(['.', '..'] + entries.map(&:name)) entries.each do |entry| entry.mock(path) @@ -176,8 +196,10 @@ describe Puppet::FileServing::Fileset do MockFile = Struct.new(:name) do def mock(base_path) + extend Mocha::API path = File.join(base_path, name) - File.stubs(:lstat).with(path).returns(MockStat.new(path, false)) + stub_file = stub(path, :lstat => MockStat.new(path, false)) + Puppet::FileSystem::File.stubs(:new).with(path).returns stub_file end end @@ -239,14 +261,14 @@ describe Puppet::FileServing::Fileset do @fileset.files.find { |file| file.include?(".svn") or file.include?("CVS") }.should be_nil end - it "uses File.stat if :links is set to :follow" do + it "uses Puppet::FileSystem::File#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 File.lstat if :links is set to :manage" do + it "uses Puppet::FileSystem::File#lstat if :links is set to :manage" do mock_dir_structure(@path, :lstat) @fileset.recurse = true @fileset.links = :manage @@ -255,7 +277,9 @@ describe Puppet::FileServing::Fileset do it "works when paths have regexp significant characters" do @path = make_absolute("/my/path/rV1x2DafFr0R6tGG+1bbk++++TM") - File.expects(:lstat).with(@path).returns stub("stat", :directory? => true) + stat = stub('dir_stat', :directory? => true) + stub_file = stub(@path, :stat => stat, :lstat => stat) + Puppet::FileSystem::File.expects(:new).with(@path).twice.returns stub_file @fileset = Puppet::FileServing::Fileset.new(@path) mock_dir_structure(@path) @fileset.recurse = true @@ -267,9 +291,13 @@ describe Puppet::FileServing::Fileset do path = make_absolute("/my/path") stat = stub 'stat', :directory? => true - File.expects(:lstat).with(path).returns(stat) - File.expects(:stat).with(path).returns(stat) - File.expects(:stat).with(File.join(path, "mylink")).raises(Errno::ENOENT) + mock_file = mock(path, :lstat => stat, :stat => stat) + Puppet::FileSystem::File.expects(:new).with(path).twice.returns mock_file + + link_path = File.join(path, "mylink") + mock_link = mock(link_path) + Puppet::FileSystem::File.expects(:new).with(link_path).returns mock_link + mock_link.expects(:stat).raises(Errno::ENOENT) Dir.stubs(:entries).with(path).returns(["mylink"]) @@ -284,10 +312,12 @@ describe Puppet::FileServing::Fileset do context "when merging other filesets" do before do @paths = [make_absolute("/first/path"), make_absolute("/second/path"), make_absolute("/third/path")] - File.stubs(:lstat).returns stub("stat", :directory? => false) + stub_file = stub(:lstat => stub('stat', :directory? => false)) + Puppet::FileSystem::File.stubs(:new).returns stub_file @filesets = @paths.collect do |path| - File.stubs(:lstat).with(path).returns stub("stat", :directory? => true) + stub_dir = stub(path, :lstat => stub('stat', :directory? => true)) + Puppet::FileSystem::File.stubs(:new).with(path).returns stub_dir Puppet::FileServing::Fileset.new(path, :recurse => true) end diff --git a/spec/unit/file_serving/metadata_spec.rb b/spec/unit/file_serving/metadata_spec.rb index 28d48e4ec..8b74d4b7e 100755 --- a/spec/unit/file_serving/metadata_spec.rb +++ b/spec/unit/file_serving/metadata_spec.rb @@ -3,6 +3,21 @@ require 'spec_helper' require 'puppet/file_serving/metadata' +# the json-schema gem doesn't support windows +if not Puppet.features.microsoft_windows? + FILE_METADATA_SCHEMA = JSON.parse(File.read(File.join(File.dirname(__FILE__), '../../../api/schemas/file_metadata.json'))) + + describe "catalog schema" do + it "should validate against the json meta-schema" do + JSON::Validator.validate!(JSON_META_SCHEMA, FILE_METADATA_SCHEMA) + end + end + + def validate_json_for_file_metadata(file_metadata) + JSON::Validator.validate!(FILE_METADATA_SCHEMA, file_metadata.to_pson) + end +end + describe Puppet::FileServing::Metadata do let(:foobar) { File.expand_path('/foo/bar') } @@ -84,7 +99,6 @@ describe Puppet::FileServing::Metadata do it "should pass the checksum in the hash verbatim as the checksum's value" do metadata.to_pson_data_hash['data']['checksum']['value'].should == metadata.checksum end - end end @@ -139,6 +153,10 @@ describe Puppet::FileServing::Metadata do metadata.checksum.should == "{mtime}#{@time}" end end + + it "should validate against the schema", :unless => Puppet.features.microsoft_windows? do + validate_json_for_file_metadata(metadata) + end end describe "when managing directories" do @@ -160,25 +178,45 @@ describe Puppet::FileServing::Metadata do metadata.collect metadata.checksum.should == "{ctime}#{time}" end + + it "should validate against the schema", :unless => Puppet.features.microsoft_windows? do + metadata.collect + validate_json_for_file_metadata(metadata) + end end + end + end - describe "when managing links", :unless => Puppet.features.microsoft_windows? do + shared_examples_for "metadata collector symlinks" do + + let(:metadata) do + data = described_class.new(path) + data.collect + data + end + + describe "when collecting attributes" do + describe "when managing links" do # 'path' is a link that points to 'target' let(:path) { tmpfile('file_serving_metadata_link') } let(:target) { tmpfile('file_serving_metadata_target') } let(:checksum) { Digest::MD5.hexdigest("some content\n") } - let(:fmode) { File.lstat(path).mode & 0777 } + let(:fmode) { Puppet::FileSystem::File.new(path).lstat.mode & 0777 } before :each do File.open(target, "wb") {|f| f.print("some content\n")} set_mode(0644, target) - FileUtils.symlink(target, path) + Puppet::FileSystem::File.new(target).symlink(path) end it "should read links instead of returning their checksums" do metadata.destination.should == target end + + it "should validate against the schema", :unless => Puppet.features.microsoft_windows? do + validate_json_for_file_metadata(metadata) + end end end @@ -209,6 +247,10 @@ describe Puppet::FileServing::Metadata do proc { metadata.collect}.should raise_error(Errno::ENOENT) end + + it "should validate against the schema", :unless => Puppet.features.microsoft_windows? do + validate_json_for_file_metadata(metadata) + end end end @@ -222,6 +264,7 @@ describe Puppet::FileServing::Metadata do end it_should_behave_like "metadata collector" + it_should_behave_like "metadata collector symlinks" def set_mode(mode, path) File.chmod(mode, path) @@ -239,6 +282,7 @@ describe Puppet::FileServing::Metadata do end it_should_behave_like "metadata collector" + it_should_behave_like "metadata collector symlinks" if Puppet.features.manages_symlinks? describe "if ACL metadata cannot be collected" do let(:path) { tmpdir('file_serving_metadata_acl') } @@ -274,16 +318,24 @@ describe Puppet::FileServing::Metadata do end -describe Puppet::FileServing::Metadata, " when pointing to a link", :unless => Puppet.features.microsoft_windows? do +describe Puppet::FileServing::Metadata, " when pointing to a link", :if => Puppet.features.manages_symlinks? do describe "when links are managed" do before do - @file = Puppet::FileServing::Metadata.new("/base/path/my/file", :links => :manage) - File.expects(:lstat).with("/base/path/my/file").returns stub("stat", :uid => 1, :gid => 2, :ftype => "link", :mode => 0755) - File.expects(:readlink).with("/base/path/my/file").returns "/some/other/path" - + path = "/base/path/my/file" + @file = Puppet::FileServing::Metadata.new(path, :links => :manage) + stat = stub("stat", :uid => 1, :gid => 2, :ftype => "link", :mode => 0755) + stub_file = stub(:readlink => "/some/other/path", :lstat => stat) + Puppet::FileSystem::File.expects(:new).with(path).at_least_once.returns stub_file @checksum = Digest::MD5.hexdigest("some content\n") # Remove these when :managed links are no longer checksumed. @file.stubs(:md5_file).returns(@checksum) # + + if Puppet.features.microsoft_windows? + win_stat = stub('win_stat', :owner => 'snarf', :group => 'thundercats', + :ftype => 'link', :mode => 0755) + Puppet::FileServing::Metadata::WindowsStat.stubs(:new).returns win_stat + end end + it "should store the destination of the link in :destination if links are :manage" do @file.collect @file.destination.should == "/some/other/path" @@ -301,9 +353,19 @@ describe Puppet::FileServing::Metadata, " when pointing to a link", :unless => P describe "when links are followed" do before do - @file = Puppet::FileServing::Metadata.new("/base/path/my/file", :links => :follow) - File.expects(:stat).with("/base/path/my/file").returns stub("stat", :uid => 1, :gid => 2, :ftype => "file", :mode => 0755) - File.expects(:readlink).with("/base/path/my/file").never + path = "/base/path/my/file" + @file = Puppet::FileServing::Metadata.new(path, :links => :follow) + stat = stub("stat", :uid => 1, :gid => 2, :ftype => "file", :mode => 0755) + mocked_file = mock(path, :stat => stat) + Puppet::FileSystem::File.expects(:new).with(path).at_least_once.returns mocked_file + mocked_file.expects(:readlink).never + + if Puppet.features.microsoft_windows? + win_stat = stub('win_stat', :owner => 'snarf', :group => 'thundercats', + :ftype => 'file', :mode => 0755) + Puppet::FileServing::Metadata::WindowsStat.stubs(:new).returns win_stat + end + @checksum = Digest::MD5.hexdigest("some content\n") @file.stubs(:md5_file).returns(@checksum) end diff --git a/spec/unit/file_serving/mount/file_spec.rb b/spec/unit/file_serving/mount/file_spec.rb index 59394be46..5821f3f98 100755 --- a/spec/unit/file_serving/mount/file_spec.rb +++ b/spec/unit/file_serving/mount/file_spec.rb @@ -85,7 +85,7 @@ describe Puppet::FileServing::Mount::File do include FileServingMountTesting before do - FileTest.stubs(:exist?).returns(true) + Puppet::FileSystem::File.stubs(:exist?).returns(true) FileTest.stubs(:directory?).returns(true) FileTest.stubs(:readable?).returns(true) @mount = Puppet::FileServing::Mount::File.new("test") @@ -95,12 +95,12 @@ describe Puppet::FileServing::Mount::File do end it "should return nil if the file is absent" do - FileTest.stubs(:exist?).returns(false) + Puppet::FileSystem::File.stubs(:exist?).returns(false) @mount.complete_path("/my/path", nil).should be_nil end it "should write a log message if the file is absent" do - FileTest.stubs(:exist?).returns(false) + Puppet::FileSystem::File.stubs(:exist?).returns(false) Puppet.expects(:info).with("File does not exist or is not accessible: /mount/my/path") @@ -108,12 +108,12 @@ describe Puppet::FileServing::Mount::File do end it "should return the file path if the file is present" do - FileTest.stubs(:exist?).with("/my/path").returns(true) + Puppet::FileSystem::File.stubs(:exist?).with("/my/path").returns(true) @mount.complete_path("/my/path", nil).should == "/mount/my/path" end it "should treat a nil file name as the path to the mount itself" do - FileTest.stubs(:exist?).returns(true) + Puppet::FileSystem::File.stubs(:exist?).returns(true) @mount.complete_path(nil, nil).should == "/mount" end @@ -141,7 +141,7 @@ describe Puppet::FileServing::Mount::File do include FileServingMountTesting before do - FileTest.stubs(:exist?).returns(true) + Puppet::FileSystem::File.stubs(:exist?).returns(true) FileTest.stubs(:directory?).returns(true) FileTest.stubs(:readable?).returns(true) @mount = Puppet::FileServing::Mount::File.new("test") @@ -153,7 +153,7 @@ describe Puppet::FileServing::Mount::File do end it "should return the results of the complete file path" do - FileTest.stubs(:exist?).returns(false) + Puppet::FileSystem::File.stubs(:exist?).returns(false) @mount.expects(:complete_path).with("/my/path", "foo").returns "eh" @mount.find("/my/path", @request).should == "eh" end @@ -163,7 +163,7 @@ describe Puppet::FileServing::Mount::File do include FileServingMountTesting before do - FileTest.stubs(:exist?).returns(true) + Puppet::FileSystem::File.stubs(:exist?).returns(true) FileTest.stubs(:directory?).returns(true) FileTest.stubs(:readable?).returns(true) @mount = Puppet::FileServing::Mount::File.new("test") @@ -175,13 +175,13 @@ describe Puppet::FileServing::Mount::File do end it "should return the results of the complete file path as an array" do - FileTest.stubs(:exist?).returns(false) + Puppet::FileSystem::File.stubs(:exist?).returns(false) @mount.expects(:complete_path).with("/my/path", "foo").returns "eh" @mount.search("/my/path", @request).should == ["eh"] end it "should return nil if the complete path is nil" do - FileTest.stubs(:exist?).returns(false) + Puppet::FileSystem::File.stubs(:exist?).returns(false) @mount.expects(:complete_path).with("/my/path", "foo").returns nil @mount.search("/my/path", @request).should be_nil end diff --git a/spec/unit/file_serving/mount/pluginfacts_spec.rb b/spec/unit/file_serving/mount/pluginfacts_spec.rb new file mode 100755 index 000000000..a54e45e4c --- /dev/null +++ b/spec/unit/file_serving/mount/pluginfacts_spec.rb @@ -0,0 +1,73 @@ +#! /usr/bin/env ruby +require 'spec_helper' +require 'puppet/file_serving/mount/pluginfacts' + +describe Puppet::FileServing::Mount::PluginFacts do + before do + @mount = Puppet::FileServing::Mount::PluginFacts.new("pluginfacts") + + @environment = stub 'environment', :module => nil + @options = { :recurse => true } + @request = stub 'request', :environment => @environment, :options => @options + end + + describe "when finding files" do + it "should use the provided environment to find the modules" do + @environment.expects(:modules).returns [] + + @mount.find("foo", @request) + end + + it "should return nil if no module can be found with a matching plugin" do + mod = mock 'module' + mod.stubs(:pluginfact).with("foo/bar").returns nil + + @environment.stubs(:modules).returns [mod] + @mount.find("foo/bar", @request).should be_nil + end + + it "should return the file path from the module" do + mod = mock 'module' + mod.stubs(:pluginfact).with("foo/bar").returns "eh" + + @environment.stubs(:modules).returns [mod] + @mount.find("foo/bar", @request).should == "eh" + end + end + + describe "when searching for files" do + it "should use the node's environment to find the modules" do + @environment.expects(:modules).at_least_once.returns [] + @environment.stubs(:modulepath).returns ["/tmp/modules"] + + @mount.search("foo", @request) + end + + it "should return modulepath if no modules can be found that have plugins" do + mod = mock 'module' + mod.stubs(:pluginfacts?).returns false + + @environment.stubs(:modules).returns [] + @environment.stubs(:modulepath).returns ["/"] + @options.expects(:[]=).with(:recurse, false) + @mount.search("foo/bar", @request).should == ["/"] + end + + it "should return nil if no modules can be found that have plugins and modulepath is invalid" do + mod = mock 'module' + mod.stubs(:pluginfacts?).returns false + + @environment.stubs(:modules).returns [] + @environment.stubs(:modulepath).returns [] + @mount.search("foo/bar", @request).should be_nil + end + + it "should return the plugin paths for each module that has plugins" do + one = stub 'module', :pluginfacts? => true, :plugin_fact_directory => "/one" + two = stub 'module', :pluginfacts? => true, :plugin_fact_directory => "/two" + + @environment.stubs(:modules).returns [one, two] + @mount.search("foo/bar", @request).should == %w{/one /two} + end + end +end diff --git a/spec/unit/file_system/file_spec.rb b/spec/unit/file_system/file_spec.rb new file mode 100644 index 000000000..564d4096c --- /dev/null +++ b/spec/unit/file_system/file_spec.rb @@ -0,0 +1,486 @@ +require 'spec_helper' +require 'puppet/file_system' +require 'puppet/util/platform' + +describe Puppet::FileSystem::File do + include PuppetSpec::Files + + context "#exclusive_open" do + it "opens ands allows updating of an existing file" do + file = Puppet::FileSystem::File.new(file_containing("file_to_update", "the contents")) + + file.exclusive_open(0660, 'r+') do |fh| + old = fh.read + fh.truncate(0) + fh.rewind + fh.write("updated #{old}") + end + + expect(file.read).to eq("updated the contents") + end + + it "opens, creates ands allows updating of a new file" do + file = Puppet::FileSystem::File.new(tmpfile("file_to_update")) + + file.exclusive_open(0660, 'w') do |fh| + fh.write("updated new file") + end + + expect(file.read).to eq("updated new file") + end + + it "excludes other processes from updating at the same time", :unless => Puppet::Util::Platform.windows? do + file = Puppet::FileSystem::File.new(file_containing("file_to_update", "0")) + + increment_counter_in_multiple_processes(file, 5, 'r+') + + expect(file.read).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 = Puppet::FileSystem::File.new(tmpfile("file_to_update")) + + increment_counter_in_multiple_processes(file, 5, 'a+') + + expect(file.read).to eq("5") + end + + it "times out if the lock cannot be aquired 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::File.new(file).exclusive_open(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::File.new(file).exclusive_open(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 = [] + 5.times do |number| + children << Kernel.fork do + file.exclusive_open(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) { Puppet::FileSystem::File.new(tmpfile("somefile")) } + let (:missing_file) { Puppet::FileSystem::File.new(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.path) + end + + it "should raise an error when trying to create a symlink" do + expect { file.symlink('foo') }.to raise_error(Puppet::Util::Windows::Error) + end + + it "should return false when trying to check if a path is a symlink" do + file.symlink?.should be_false + end + + it "should raise an error when trying to read a symlink" do + expect { file.readlink }.to raise_error(Puppet::Util::Windows::Error) + end + + it "should return a File::Stat instance when calling stat on an existing file" do + file.stat.should be_instance_of(File::Stat) + end + + it "should raise Errno::ENOENT when calling stat on a missing file" do + expect { missing_file.stat }.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(file.path) + + file.lstat + end + end + + describe "symlink", :if => Puppet.features.manages_symlinks? do + + let (:file) { Puppet::FileSystem::File.new(tmpfile("somefile")) } + let (:missing_file) { Puppet::FileSystem::File.new(tmpfile("missingfile")) } + let (:dir) { Puppet::FileSystem::File.new(tmpdir("somedir")) } + + before :each do + FileUtils.touch(file.path) + end + + it "should return true for exist? on a present file" do + file.exist?.should be_true + Puppet::FileSystem::File.exist?(file.path).should be_true + end + + it "should return false for exist? on a non-existant file" do + missing_file.exist?.should be_false + Puppet::FileSystem::File.exist?(missing_file.path).should be_false + end + + it "should return true for exist? on a present directory" do + dir.exist?.should be_true + Puppet::FileSystem::File.exist?(dir.path).should be_true + end + + it "should return false for exist? on a dangling symlink" do + symlink = Puppet::FileSystem::File.new(tmpfile("somefile_link")) + missing_file.symlink(symlink.path) + + missing_file.exist?.should be_false + symlink.exist?.should be_false + end + + it "should return true for exist? on valid symlinks" do + [file, dir].each do |target| + symlink = Puppet::FileSystem::File.new(tmpfile("#{target.path.basename.to_s}_link")) + target.symlink(symlink.path) + + target.exist?.should be_true + symlink.exist?.should be_true + end + end + + it "should not create a symlink when the :noop option is specified" do + [file, dir].each do |target| + symlink = Puppet::FileSystem::File.new(tmpfile("#{target.path.basename.to_s}_link")) + target.symlink(symlink.path, { :noop => true }) + + target.exist?.should be_true + symlink.exist?.should be_false + 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 = Puppet::FileSystem::File.new(tmpfile("#{file.path.basename.to_s}_link")) + FileUtils.touch(existing_file.path) + + [file, dir].each do |target| + expect { target.symlink(existing_file.path) }.to raise_error(Errno::EEXIST) + + existing_file.exist?.should be_true + existing_file.symlink?.should be_false + 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 = Puppet::FileSystem::File.new(tmpdir("#{file.path.basename.to_s}_dir")) + + [file, dir].each do |target| + target.symlink(existing_dir.path).should == 0 + + existing_dir.exist?.should be_true + File.directory?(existing_dir.path).should be_true + existing_dir.symlink?.should be_false + 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 = Puppet::FileSystem::File.new(tmpdir("#{target.path.basename.to_s}_dir")) + symlink = Puppet::FileSystem::File.new(tmpfile("#{existing_dir.path.basename.to_s}_link")) + existing_dir.symlink(symlink.path) + + symlink.readlink.should == existing_dir.path.to_s + + # now try to point it at the new target, no error raised, but file system unchanged + target.symlink(symlink.path).should == 0 + symlink.readlink.should == existing_dir.path.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 = Puppet::FileSystem::File.new(tmpfile("#{file.path.basename.to_s}_link")) + file_2 = Puppet::FileSystem::File.new(tmpfile("#{file.path.basename.to_s}_2")) + FileUtils.touch(file_2.path) + # symlink -> file_2 + file_2.symlink(symlink.path) + + [file, dir].each do |target| + expect { target.symlink(symlink.path) }.to raise_error(Errno::EEXIST) + symlink.readlink.should == file_2.path.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 = Puppet::FileSystem::File.new(tmpfile("#{target.path.basename.to_s}_existing")) + FileUtils.touch(existing_file.path) + existing_file.symlink?.should be_false + + target.symlink(existing_file.path, { :force => true }) + + existing_file.symlink?.should be_true + existing_file.readlink.should == target.path.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 = Puppet::FileSystem::File.new(tmpfile("#{target.path.basename.to_s}_existing")) + FileUtils.touch(existing_file.path) + existing_symlink = Puppet::FileSystem::File.new(tmpfile("#{existing_file.path.basename.to_s}_link")) + existing_file.symlink(existing_symlink.path) + + existing_symlink.readlink.should == existing_file.path.to_s + + target.symlink(existing_symlink.path, { :force => true }) + + existing_symlink.readlink.should == target.path.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 = Puppet::FileSystem::File.new(tmpdir("#{target.path.basename.to_s}_existing")) + + target.symlink(existing_dir.path, { :force => true }).should == 0 + + existing_dir.symlink?.should be_false + 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 = Puppet::FileSystem::File.new(tmpdir("#{target.path.basename.to_s}_existing")) + existing_symlink = Puppet::FileSystem::File.new(tmpfile("#{existing_dir.path.basename.to_s}_link")) + existing_dir.symlink(existing_symlink.path) + + existing_symlink.readlink.should == existing_dir.path.to_s + + target.symlink(existing_symlink.path, { :force => true }).should == 0 + + existing_symlink.readlink.should == existing_dir.path.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::File.exist?(f).should be_false } + end + + it "should return a File::Stat instance when calling stat on an existing file" do + file.stat.should be_instance_of(File::Stat) + end + + it "should raise Errno::ENOENT when calling stat on a missing file" do + expect { missing_file.stat }.to raise_error(Errno::ENOENT) + end + + it "should be able to create a symlink, and verify it with symlink?" do + symlink = Puppet::FileSystem::File.new(tmpfile("somefile_link")) + file.symlink(symlink.path) + + symlink.symlink?.should be_true + end + + it "should report symlink? as false on file, directory and missing files" do + [file, dir, missing_file].each do |f| + f.symlink?.should be_false + end + end + + it "should return a File::Stat with ftype 'link' when calling lstat on a symlink pointing to existing file" do + symlink = Puppet::FileSystem::File.new(tmpfile("somefile_link")) + file.symlink(symlink.path) + + stat = symlink.lstat + 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 = Puppet::FileSystem::File.new(tmpfile("somefile_link")) + missing_file.symlink(symlink.path) + + stat = symlink.lstat + 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 = Puppet::FileSystem::File.new(tmpfile("somefile_link")) + file.symlink(symlink.path) + + stat = symlink.stat + 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 = Puppet::FileSystem::File.new(tmpfile("somefile_link")) + dir.symlink(symlink.path) + + stat = symlink.stat + stat.should be_instance_of(File::Stat) + stat.ftype.should == 'directory' + 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 = Puppet::FileSystem::File.new(tmpfile("somefile_link")) + file.symlink(symlink.path) + + # point symlink2 -> symlink + symlink2 = Puppet::FileSystem::File.new(tmpfile("somefile_link2")) + symlink.symlink(symlink2.path) + + symlink2.stat.ftype.should == 'file' + end + + + it "should raise Errno::ENOENT when calling stat on a dangling symlink" do + symlink = Puppet::FileSystem::File.new(tmpfile("somefile_link")) + missing_file.symlink(symlink.path) + + expect { symlink.stat }.to raise_error(Errno::ENOENT) + end + + it "should be able to readlink to resolve the physical path to a symlink" do + symlink = Puppet::FileSystem::File.new(tmpfile("somefile_link")) + file.symlink(symlink.path) + + file.exist?.should be_true + symlink.readlink.should == file.path.to_s + end + + it "should not resolve entire symlink chain with readlink on a symlink'd symlink" do + # point symlink -> file + symlink = Puppet::FileSystem::File.new(tmpfile("somefile_link")) + file.symlink(symlink.path) + + # point symlink2 -> symlink + symlink2 = Puppet::FileSystem::File.new(tmpfile("somefile_link2")) + symlink.symlink(symlink2.path) + + file.exist?.should be_true + symlink2.readlink.should == symlink.path.to_s + end + + it "should be able to readlink to resolve the physical path to a dangling symlink" do + symlink = Puppet::FileSystem::File.new(tmpfile("somefile_link")) + missing_file.symlink(symlink.path) + + missing_file.exist?.should be_false + symlink.readlink.should == missing_file.path.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 = Puppet::FileSystem::File.new(tmpfile("#{target.path.basename.to_s}_link")) + target.symlink(symlink.path) + + target.exist?.should be_true + symlink.readlink.should == target.path.to_s + + symlink.unlink.should == 1 # count of files + + target.exist?.should be_true + symlink.exist?.should be_false + end + end + + it "should delete only the symlink and not the target when calling unlink class method" do + [file, dir].each do |target| + symlink = Puppet::FileSystem::File.new(tmpfile("#{target.path.basename.to_s}_link")) + target.symlink(symlink.path) + + target.exist?.should be_true + symlink.readlink.should == target.path.to_s + + Puppet::FileSystem::File.unlink(symlink.path).should == 1 # count of files + + target.exist?.should be_true + symlink.exist?.should be_false + end + end + + describe "unlink" do + it "should delete files with unlink" do + file.exist?.should be_true + + file.unlink.should == 1 # count of files + + file.exist?.should be_false + end + + it "should delete files with unlink class method" do + file.exist?.should be_true + + Puppet::FileSystem::File.unlink(file.path).should == 1 # count of files + + file.exist?.should be_false + end + + it "should delete multiple files with unlink class method" do + paths = (1..3).collect do |i| + f = Puppet::FileSystem::File.new(tmpfile("somefile_#{i}")) + FileUtils.touch(f.path) + f.exist?.should be_true + f.path.to_s + end + + Puppet::FileSystem::File.unlink(*paths).should == 3 # count of files + + paths.each { |p| Puppet::FileSystem::File.exist?(p).should be_false } + end + + it "should raise Errno::EPERM or Errno::EISDIR when trying to delete a directory with the unlink class method" do + dir.exist?.should be_true + + ex = nil + begin + Puppet::FileSystem::File.unlink(dir.path) + rescue Exception => e + ex = e + end + + [ + Errno::EPERM, # Windows and OSX + Errno::EISDIR # Linux + ].should include ex.class + + dir.exist?.should be_true + end + end + end +end diff --git a/spec/unit/file_system/tempfile_spec.rb b/spec/unit/file_system/tempfile_spec.rb new file mode 100644 index 000000000..5ad0a8abc --- /dev/null +++ b/spec/unit/file_system/tempfile_spec.rb @@ -0,0 +1,48 @@ +require 'spec_helper' + +describe Puppet::FileSystem::Tempfile do + it "makes the name of the file available" do + Puppet::FileSystem::Tempfile.open('foo') do |file| + expect(file.path).to match(/foo/) + end + end + + it "provides a writeable file" do + Puppet::FileSystem::Tempfile.open('foo') do |file| + file.write("stuff") + file.flush + + expect(Puppet::FileSystem::File.new(file.path).read).to eq("stuff") + end + end + + it "returns the value of the block" do + the_value = Puppet::FileSystem::Tempfile.open('foo') do |file| + "my value" + end + + expect(the_value).to eq("my value") + end + + it "unlinks the temporary file" do + filename = Puppet::FileSystem::Tempfile.open('foo') do |file| + file.path + end + + expect(Puppet::FileSystem::File.new(filename).exist?).to be_false + end + + it "unlinks the temporary file even if the block raises an error" do + filename = nil + + begin + Puppet::FileSystem::Tempfile.open('foo') do |file| + filename = file.path + raise "error!" + end + rescue + end + + expect(Puppet::FileSystem::File.new(filename).exist?).to be_false + end +end diff --git a/spec/unit/graph/relationship_graph_spec.rb b/spec/unit/graph/relationship_graph_spec.rb index 7a698d769..8bd35de4d 100755 --- a/spec/unit/graph/relationship_graph_spec.rb +++ b/spec/unit/graph/relationship_graph_spec.rb @@ -165,12 +165,6 @@ describe Puppet::Graph::RelationshipGraph do expect(order_resources_traversed_in(relationships)).to( include_in_order("Notify[a]", "Notify[b]", "Notify[c]", "Notify[d]")) end - - def order_resources_traversed_in(relationships) - order_seen = [] - relationships.traverse { |resource| order_seen << resource.ref } - order_seen - end end describe "when interrupting traversal" do diff --git a/spec/unit/hiera_puppet_spec.rb b/spec/unit/hiera_puppet_spec.rb index 6c0f882a0..894b6e334 100644 --- a/spec/unit/hiera_puppet_spec.rb +++ b/spec/unit/hiera_puppet_spec.rb @@ -52,7 +52,7 @@ describe 'HieraPuppet' do pending("This example does not apply to Puppet #{Puppet.version} because it does not have this setting") end - File.stubs(:exist?).with(Puppet[:hiera_config]).returns(true) + Puppet::FileSystem::File.stubs(:exist?).with(Puppet[:hiera_config]).returns(true) HieraPuppet.send(:hiera_config_file).should == Puppet[:hiera_config] end @@ -64,7 +64,7 @@ describe 'HieraPuppet' do end Puppet.settings[:confdir] = "/dev/null/puppet" hiera_config = File.join(Puppet[:confdir], 'hiera.yaml') - File.stubs(:exist?).with(hiera_config).returns(true) + Puppet::FileSystem::File.stubs(:exist?).with(hiera_config).returns(true) HieraPuppet.send(:hiera_config_file).should == hiera_config end diff --git a/spec/unit/indirector/catalog/compiler_spec.rb b/spec/unit/indirector/catalog/compiler_spec.rb index ff32f7c7b..c968794e4 100755 --- a/spec/unit/indirector/catalog/compiler_spec.rb +++ b/spec/unit/indirector/catalog/compiler_spec.rb @@ -24,12 +24,12 @@ describe Puppet::Resource::Catalog::Compiler do end it "should cache the server metadata and reuse it" do + Puppet[:node_terminus] = :memory + Puppet::Node.indirection.save(Puppet::Node.new("node1")) + Puppet::Node.indirection.save(Puppet::Node.new("node2")) + compiler = Puppet::Resource::Catalog::Compiler.new - node1 = stub 'node1', :merge => nil - node2 = stub 'node2', :merge => nil compiler.stubs(:compile) - Puppet::Node.indirection.stubs(:find).with('node1', has_entry(:environment => anything)).returns(node1) - Puppet::Node.indirection.stubs(:find).with('node2', has_entry(:environment => anything)).returns(node2) compiler.find(Puppet::Indirector::Request.new(:catalog, :find, 'node1', nil, :node => 'node1')) compiler.find(Puppet::Indirector::Request.new(:catalog, :find, 'node2', nil, :node => 'node2')) @@ -175,19 +175,16 @@ describe Puppet::Resource::Catalog::Compiler do end describe "when finding nodes" do - before do + it "should look node information up via the Node class with the provided key" do Facter.stubs(:value).returns("whatever") - @compiler = Puppet::Resource::Catalog::Compiler.new - @name = "me" - @node = mock 'node' - @request = Puppet::Indirector::Request.new(:catalog, :find, @name, nil) - @compiler.stubs(:compile) - end + node = Puppet::Node.new('node') + compiler = Puppet::Resource::Catalog::Compiler.new + request = Puppet::Indirector::Request.new(:catalog, :find, "me", nil) + compiler.stubs(:compile) - it "should look node information up via the Node class with the provided key" do - @node.stubs :merge - Puppet::Node.indirection.expects(:find).with(@name, anything).returns(@node) - @compiler.find(@request) + Puppet::Node.indirection.expects(:find).with("me", anything).returns(node) + + compiler.find(request) end end @@ -197,11 +194,10 @@ describe Puppet::Resource::Catalog::Compiler do Facter.expects(:value).with('fqdn').returns("my.server.com") Facter.expects(:value).with('ipaddress').returns("my.ip.address") @compiler = Puppet::Resource::Catalog::Compiler.new - @name = "me" - @node = mock 'node' - @request = Puppet::Indirector::Request.new(:catalog, :find, @name, nil) + @node = Puppet::Node.new("me") + @request = Puppet::Indirector::Request.new(:catalog, :find, "me", nil) @compiler.stubs(:compile) - Puppet::Node.indirection.stubs(:find).with(@name, anything).returns(@node) + Puppet::Node.indirection.stubs(:find).with("me", anything).returns(@node) end it "should add the server's Puppet version to the node's parameters as 'serverversion'" do diff --git a/spec/unit/indirector/certificate_status/file_spec.rb b/spec/unit/indirector/certificate_status/file_spec.rb index c880bf786..83d4de210 100755 --- a/spec/unit/indirector/certificate_status/file_spec.rb +++ b/spec/unit/indirector/certificate_status/file_spec.rb @@ -87,16 +87,14 @@ describe "Puppet::Indirector::CertificateStatus::File" do retrieved_host.certificate_request.content.to_s.chomp.should == @host.certificate_request.content.to_s.chomp end - it "should return the Puppet::SSL::Host when a public key exist for the host" do - pending("porting to Windows", :if => Puppet.features.microsoft_windows?) do - generate_signed_cert(@host) - request = Puppet::Indirector::Request.new(:certificate_status, :find, "foo", @host) + it "should return the Puppet::SSL::Host when a public key exists for the host" do + generate_signed_cert(@host) + request = Puppet::Indirector::Request.new(:certificate_status, :find, "foo", @host) - retrieved_host = @terminus.find(request) + retrieved_host = @terminus.find(request) - retrieved_host.name.should == @host.name - retrieved_host.certificate.content.to_s.chomp.should == @host.certificate.content.to_s.chomp - end + retrieved_host.name.should == @host.name + retrieved_host.certificate.content.to_s.chomp.should == @host.certificate.content.to_s.chomp end it "should return nil when neither a CSR nor public key exist for the host" do @@ -122,12 +120,10 @@ describe "Puppet::Indirector::CertificateStatus::File" do end it "should sign the on-disk CSR when it is present" do - pending("porting to Windows", :if => Puppet.features.microsoft_windows?) do - signed_host = generate_signed_cert(@host) + signed_host = generate_signed_cert(@host) - signed_host.state.should == "signed" - Puppet::SSL::Certificate.indirection.find("foobar").should be_instance_of(Puppet::SSL::Certificate) - end + signed_host.state.should == "signed" + Puppet::SSL::Certificate.indirection.find("foobar").should be_instance_of(Puppet::SSL::Certificate) end end @@ -142,11 +138,9 @@ describe "Puppet::Indirector::CertificateStatus::File" do end it "should revoke the certificate when it is present" do - pending("porting to Windows", :if => Puppet.features.microsoft_windows?) do - generate_revoked_cert(@host) + generate_revoked_cert(@host) - @host.state.should == 'revoked' - end + @host.state.should == 'revoked' end end end @@ -163,39 +157,35 @@ describe "Puppet::Indirector::CertificateStatus::File" do end it "should clean certs, cert requests, keys" do - pending("porting to Windows", :if => Puppet.features.microsoft_windows?) do - signed_host = Puppet::SSL::Host.new("clean_signed_cert") - generate_signed_cert(signed_host) - signed_request = Puppet::Indirector::Request.new(:certificate_status, :delete, "clean_signed_cert", signed_host) - @terminus.destroy(signed_request).should == "Deleted for clean_signed_cert: Puppet::SSL::Certificate, Puppet::SSL::Key" - - requested_host = Puppet::SSL::Host.new("clean_csr") - generate_csr(requested_host) - csr_request = Puppet::Indirector::Request.new(:certificate_status, :delete, "clean_csr", requested_host) - @terminus.destroy(csr_request).should == "Deleted for clean_csr: Puppet::SSL::CertificateRequest, Puppet::SSL::Key" - end + signed_host = Puppet::SSL::Host.new("clean_signed_cert") + generate_signed_cert(signed_host) + signed_request = Puppet::Indirector::Request.new(:certificate_status, :delete, "clean_signed_cert", signed_host) + @terminus.destroy(signed_request).should == "Deleted for clean_signed_cert: Puppet::SSL::Certificate, Puppet::SSL::Key" + + requested_host = Puppet::SSL::Host.new("clean_csr") + generate_csr(requested_host) + csr_request = Puppet::Indirector::Request.new(:certificate_status, :delete, "clean_csr", requested_host) + @terminus.destroy(csr_request).should == "Deleted for clean_csr: Puppet::SSL::CertificateRequest, Puppet::SSL::Key" end end describe "when searching" do it "should return a list of all hosts with certificate requests, signed certs, or revoked certs" do - pending("porting to Windows", :if => Puppet.features.microsoft_windows?) do - Puppet.settings.use(:main) + Puppet.settings.use(:main) - signed_host = Puppet::SSL::Host.new("signed_host") - generate_signed_cert(signed_host) + signed_host = Puppet::SSL::Host.new("signed_host") + generate_signed_cert(signed_host) - requested_host = Puppet::SSL::Host.new("requested_host") - generate_csr(requested_host) + requested_host = Puppet::SSL::Host.new("requested_host") + generate_csr(requested_host) - revoked_host = Puppet::SSL::Host.new("revoked_host") - generate_revoked_cert(revoked_host) + revoked_host = Puppet::SSL::Host.new("revoked_host") + generate_revoked_cert(revoked_host) - retrieved_hosts = @terminus.search(Puppet::Indirector::Request.new(:certificate_status, :search, "all", signed_host)) + retrieved_hosts = @terminus.search(Puppet::Indirector::Request.new(:certificate_status, :search, "all", signed_host)) - results = retrieved_hosts.map {|h| [h.name, h.state]}.sort{ |h,i| h[0] <=> i[0] } - results.should == [["ca","signed"],["requested_host","requested"],["revoked_host","revoked"],["signed_host","signed"]] - end + results = retrieved_hosts.map {|h| [h.name, h.state]}.sort{ |h,i| h[0] <=> i[0] } + results.should == [["ca","signed"],["requested_host","requested"],["revoked_host","revoked"],["signed_host","signed"]] end end end diff --git a/spec/unit/indirector/data_binding/hiera_spec.rb b/spec/unit/indirector/data_binding/hiera_spec.rb index 98883bb9d..7ab3b27fc 100644 --- a/spec/unit/indirector/data_binding/hiera_spec.rb +++ b/spec/unit/indirector/data_binding/hiera_spec.rb @@ -2,8 +2,32 @@ require 'spec_helper' require 'puppet/indirector/data_binding/hiera' describe Puppet::DataBinding::Hiera do - it "should be a subclass of the Hiera terminus" do - Puppet::DataBinding::Hiera.superclass.should equal(Puppet::Indirector::Hiera) + include PuppetSpec::Files + + def write_hiera_config(config_file, datadir) + File.open(config_file, 'w') do |f| + f.write("--- + :yaml: + :datadir: #{datadir} + :hierarchy: ['global', 'invalid'] + :logger: 'noop' + :backends: ['yaml'] + ") + end + end + + def request(key) + Puppet::Indirector::Request.new(:hiera, :find, key, nil) + end + + before do + hiera_config_file = tmpfile("hiera.yaml") + Puppet.settings[:hiera_config] = hiera_config_file + write_hiera_config(hiera_config_file, my_fixture_dir) + end + + after do + Puppet::DataBinding::Hiera.instance_variable_set(:@hiera, nil) end it "should have documentation" do @@ -18,4 +42,73 @@ describe Puppet::DataBinding::Hiera do it "should have its name set to :hiera" do Puppet::DataBinding::Hiera.name.should == :hiera end + it "should be the default data_binding terminus" do + Puppet.settings[:data_binding_terminus].should == :hiera + end + + it "should raise an error if we don't have the hiera feature" do + Puppet.features.expects(:hiera?).returns(false) + lambda { Puppet::DataBinding::Hiera.new }.should raise_error RuntimeError, + "Hiera terminus not supported without hiera library" + end + + describe "the behavior of the hiera_config method", :if => Puppet.features.hiera? do + it "should override the logger and set it to puppet" do + Puppet::DataBinding::Hiera.hiera_config[:logger].should == "puppet" + end + + context "when the Hiera configuration file does not exist" do + let(:path) { File.expand_path('/doesnotexist') } + + before do + Puppet.settings[:hiera_config] = path + end + + it "should log a warning" do + Puppet.expects(:warning).with( + "Config file #{path} not found, using Hiera defaults") + Puppet::DataBinding::Hiera.hiera_config + end + + it "should only configure the logger and set it to puppet" do + Puppet.expects(:warning).with( + "Config file #{path} not found, using Hiera defaults") + Puppet::DataBinding::Hiera.hiera_config.should == { :logger => 'puppet' } + end + end + end + + describe "the behavior of the find method", :if => Puppet.features.hiera? do + + let(:data_binder) { Puppet::DataBinding::Hiera.new } + + it "should support looking up an integer" do + data_binder.find(request("integer")).should == 3000 + end + + it "should support looking up a string" do + data_binder.find(request("string")).should == 'apache' + end + + it "should support looking up an array" do + data_binder.find(request("array")).should == [ + '0.ntp.puppetlabs.com', + '1.ntp.puppetlabs.com', + ] + end + + it "should support looking up a hash" do + data_binder.find(request("hash")).should == { + 'user' => 'Hightower', + 'group' => 'admin', + 'mode' => '0644' + } + end + + it "raises a data binding error if hiera cannot parse the yaml data" do + expect do + data_binder.find(request('invalid')) + end.to raise_error(Puppet::DataBinding::LookupError) + end + end end diff --git a/spec/unit/indirector/direct_file_server_spec.rb b/spec/unit/indirector/direct_file_server_spec.rb index 234f25ef9..8b08195bb 100755 --- a/spec/unit/indirector/direct_file_server_spec.rb +++ b/spec/unit/indirector/direct_file_server_spec.rb @@ -26,12 +26,12 @@ describe Puppet::Indirector::DirectFileServer do describe Puppet::Indirector::DirectFileServer, "when finding a single file" do it "should return nil if the file does not exist" do - FileTest.expects(:exists?).with(@path).returns false + Puppet::FileSystem::File.expects(:exist?).with(@path).returns false @server.find(@request).should be_nil end it "should return a Content instance created with the full path to the file if the file exists" do - FileTest.expects(:exists?).with(@path).returns true + Puppet::FileSystem::File.expects(:exist?).with(@path).returns true @model.expects(:new).returns(:mycontent) @server.find(@request).should == :mycontent end @@ -42,7 +42,7 @@ describe Puppet::Indirector::DirectFileServer do before do @data = mock 'content' @data.stubs(:collect) - FileTest.expects(:exists?).with(@path).returns true + Puppet::FileSystem::File.expects(:exist?).with(@path).returns true end it "should pass the full path to the instance" do @@ -61,18 +61,18 @@ describe Puppet::Indirector::DirectFileServer do describe Puppet::Indirector::DirectFileServer, "when searching for multiple files" do it "should return nil if the file does not exist" do - FileTest.expects(:exists?).with(@path).returns false + Puppet::FileSystem::File.expects(:exist?).with(@path).returns false @server.find(@request).should be_nil end it "should use :path2instances from the terminus_helper to return instances if the file exists" do - FileTest.expects(:exists?).with(@path).returns true + Puppet::FileSystem::File.expects(:exist?).with(@path).returns true @server.expects(:path2instances) @server.search(@request) end it "should pass the original request to :path2instances" do - FileTest.expects(:exists?).with(@path).returns true + Puppet::FileSystem::File.expects(:exist?).with(@path).returns true @server.expects(:path2instances).with(@request, @path) @server.search(@request) end diff --git a/spec/unit/indirector/facts/facter_spec.rb b/spec/unit/indirector/facts/facter_spec.rb index 3e301a5a5..4c55ec6c3 100755 --- a/spec/unit/indirector/facts/facter_spec.rb +++ b/spec/unit/indirector/facts/facter_spec.rb @@ -48,6 +48,9 @@ describe Puppet::Node::Facts::Facter do Facter.stubs(:to_hash).returns({}) @name = "me" @request = stub 'request', :key => @name + @environment = stub 'environment' + @request.stubs(:environment).returns(@environment) + @request.environment.stubs(:modules).returns([]) end describe Puppet::Node::Facts::Facter, " when finding facts" do @@ -58,6 +61,15 @@ describe Puppet::Node::Facts::Facter do @facter.find(@request) end + it "should include external facts when feature is present" do + clear = sequence 'clear' + Puppet.features.stubs(:external_facts?).returns(:true) + Puppet::Node::Facts::Facter.expects(:setup_external_facts).in_sequence(clear) + Puppet::Node::Facts::Facter.expects(:reload_facter).in_sequence(clear) + Puppet::Node::Facts::Facter.expects(:load_fact_plugins).in_sequence(clear) + @facter.find(@request) + end + it "should return a Facts instance" do @facter.find(@request).should be_instance_of(Puppet::Node::Facts) end @@ -131,6 +143,20 @@ describe Puppet::Node::Facts::Facter do Puppet::Node::Facts::Facter.load_facts_in_dir("mydir") end + it "should include pluginfactdest when loading external facts", + :if => Puppet.features.external_facts?, :unless => Puppet.features.microsoft_windows? do + Puppet[:pluginfactdest] = "/plugin/dest" + @facter.find(@request) + Facter::Util::Config.external_facts_dirs.include?("/plugin/dest") + end + + it "should include pluginfactdest when loading external facts", + :if => Puppet.features.external_facts?, :if => Puppet.features.microsoft_windows? do + Puppet[:pluginfactdest] = "/plugin/dest" + @facter.find(@request) + Facter::Util::Config.external_facts_dirs.include?("C:/plugin/dest") + end + describe "when loading fact plugins from disk" do let(:one) { File.expand_path("one") } let(:two) { File.expand_path("two") } @@ -160,5 +186,12 @@ describe Puppet::Node::Facts::Facter do Puppet::Node::Facts::Facter.load_fact_plugins end + + it "should include module plugin facts when present", :if => Puppet.features.external_facts? do + mod = Puppet::Module.new("mymodule", "#{one}/mymodule", @request.environment) + @request.environment.stubs(:modules).returns([mod]) + @facter.find(@request) + Facter::Util::Config.external_facts_dirs.include?("#{one}/mymodule/facts.d") + 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 37529e5d5..2f9e140f7 100755 --- a/spec/unit/indirector/file_bucket_file/file_spec.rb +++ b/spec/unit/indirector/file_bucket_file/file_spec.rb @@ -2,18 +2,11 @@ require 'spec_helper' require 'puppet/indirector/file_bucket_file/file' +require 'puppet/util/platform' describe Puppet::FileBucketFile::File do include PuppetSpec::Files - it "should be a subclass of the Code terminus class" do - Puppet::FileBucketFile::File.superclass.should equal(Puppet::Indirector::Code) - end - - it "should have documentation" do - Puppet::FileBucketFile::File.doc.should be_instance_of(String) - end - describe "non-stubbing tests" do include PuppetSpec::Files @@ -23,7 +16,7 @@ describe Puppet::FileBucketFile::File do def save_bucket_file(contents, path = "/who_cares") bucket_file = Puppet::FileBucket::File.new(contents) - Puppet::FileBucket::File.indirection.save(bucket_file, "md5/#{Digest::MD5.hexdigest(contents)}#{path}") + Puppet::FileBucket::File.indirection.save(bucket_file, "#{bucket_file.name}#{path}") bucket_file.checksum_data end @@ -34,17 +27,58 @@ describe Puppet::FileBucketFile::File do 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 + 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 it "should store the path if not already stored" do checksum = save_bucket_file("stuff\r\n", "/foo/bar") + dir_path = "#{Puppet[:bucketdir]}/f/c/7/7/7/c/0/b/fc777c0bc467e1ab98b4c6915af802ec" - IO.binread("#{dir_path}/contents").should == "stuff\r\n" - File.read("#{dir_path}/paths").should == "foo/bar\n" + contents_file = Puppet::FileSystem::File.new("#{dir_path}/contents") + paths_file = Puppet::FileSystem::File.new("#{dir_path}/paths") + contents_file.binread.should == "stuff\r\n" + paths_file.read.should == "foo/bar\n" end it "should leave the paths file alone if the path is already stored" do checksum = save_bucket_file("stuff", "/foo/bar") + checksum = save_bucket_file("stuff", "/foo/bar") + dir_path = "#{Puppet[:bucketdir]}/c/1/3/d/8/8/c/b/c13d88cb4cb02003daedb8a84e5d272a" File.read("#{dir_path}/contents").should == "stuff" File.read("#{dir_path}/paths").should == "foo/bar\n" @@ -52,7 +86,9 @@ describe Puppet::FileBucketFile::File do it "should store an additional path if the new path differs from those already stored" do checksum = save_bucket_file("stuff", "/foo/bar") + checksum = save_bucket_file("stuff", "/foo/baz") + dir_path = "#{Puppet[:bucketdir]}/c/1/3/d/8/8/c/b/c13d88cb4cb02003daedb8a84e5d272a" File.read("#{dir_path}/contents").should == "stuff" File.read("#{dir_path}/paths").should == "foo/bar\nfoo/baz\n" @@ -62,6 +98,7 @@ describe Puppet::FileBucketFile::File do describe "when not supplying a path" do it "should save the file and create an empty paths file" do checksum = save_bucket_file("stuff", "") + dir_path = "#{Puppet[:bucketdir]}/c/1/3/d/8/8/c/b/c13d88cb4cb02003daedb8a84e5d272a" File.read("#{dir_path}/contents").should == "stuff" File.read("#{dir_path}/paths").should == "" @@ -78,16 +115,18 @@ describe Puppet::FileBucketFile::File do 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("md5/#{checksum}/foo/baz").should == false Puppet::FileBucket::File.indirection.find("md5/#{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("md5/#{checksum}/foo/bar").should == true find_result = Puppet::FileBucket::File.indirection.find("md5/#{checksum}/foo/bar") - find_result.should be_a(Puppet::FileBucket::File) find_result.checksum.should == "{md5}#{checksum}" find_result.to_s.should == contents end @@ -105,10 +144,11 @@ describe Puppet::FileBucketFile::File do it "should return true/file if the file is already bucketed" do contents = "I'm the contents of a file" + checksum = save_bucket_file(contents, '/foo/bar') + Puppet::FileBucket::File.indirection.head("md5/#{checksum}#{trailing_string}").should == true find_result = Puppet::FileBucket::File.indirection.find("md5/#{checksum}#{trailing_string}") - find_result.should be_a(Puppet::FileBucket::File) find_result.checksum.should == "{md5}#{checksum}" find_result.to_s.should == contents end @@ -126,6 +166,7 @@ describe Puppet::FileBucketFile::File do 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("md5/#{checksum1}", :diff_with => checksum2) diff.should == <<HERE 2c2 @@ -136,27 +177,23 @@ HERE end it "should raise an exception if the hash to diff against isn't found" do - checksum = save_bucket_file("whatever") bogus_checksum = "d1bf072d0e2c6e20e3fbd23f022089a1" - lambda { Puppet::FileBucket::File.indirection.find("md5/#{checksum}", :diff_with => bogus_checksum) }.should raise_error "could not find diff_with #{bogus_checksum}" + checksum = save_bucket_file("whatever") + + expect do + Puppet::FileBucket::File.indirection.find("md5/#{checksum}", :diff_with => bogus_checksum) + end.to raise_error "could not find diff_with #{bogus_checksum}" end it "should return nil if the hash to diff from isn't found" do - checksum = save_bucket_file("whatever") bogus_checksum = "d1bf072d0e2c6e20e3fbd23f022089a1" + checksum = save_bucket_file("whatever") + Puppet::FileBucket::File.indirection.find("md5/#{bogus_checksum}", :diff_with => checksum).should == nil end end end - describe "when initializing" do - it "should use the filebucket settings section" do - Puppet.settings.expects(:use).with(:filebucket) - Puppet::FileBucketFile::File.new - end - end - - [true, false].each do |override_bucket_path| describe "when bucket path #{if override_bucket_path then 'is' else 'is not' end} overridden" do [true, false].each do |supply_path| @@ -245,32 +282,4 @@ HERE end end end - - describe "when verifying identical files" do - let(:contents) { "file\r\n contents" } - let(:digest) { "8b3702ad1aed1ace7e32bde76ffffb2d" } - let(:checksum) { "{md5}#{@digest}" } - let(:bucketdir) { tmpdir('file_bucket_file') } - let(:destdir) { "#{bucketdir}/8/b/3/7/0/2/a/d/8b3702ad1aed1ace7e32bde76ffffb2d" } - let(:bucket) { Puppet::FileBucket::File.new(contents) } - - before :each do - Puppet[:bucketdir] = bucketdir - FileUtils.mkdir_p(destdir) - end - - it "should raise an error if the files don't match" do - File.open(File.join(destdir, 'contents'), 'wb') { |f| f.print "corrupt contents" } - - lambda{ - Puppet::FileBucketFile::File.new.send(:verify_identical_file!, bucket) - }.should raise_error(Puppet::FileBucket::BucketError) - end - - it "should do nothing if the files match" do - File.open(File.join(destdir, 'contents'), 'wb') { |f| f.print contents } - - Puppet::FileBucketFile::File.new.send(:verify_identical_file!, bucket) - end - end end diff --git a/spec/unit/indirector/file_metadata/file_spec.rb b/spec/unit/indirector/file_metadata/file_spec.rb index d51c65882..459c1dbe8 100755 --- a/spec/unit/indirector/file_metadata/file_spec.rb +++ b/spec/unit/indirector/file_metadata/file_spec.rb @@ -19,7 +19,7 @@ describe Puppet::Indirector::FileMetadata::File do @uri = Puppet::Util.path_to_uri(@path).to_s @data = mock 'metadata' @data.stubs(:collect) - FileTest.expects(:exists?).with(@path).returns true + Puppet::FileSystem::File.expects(:exist?).with(@path).returns true @request = Puppet::Indirector::Request.new(:file_metadata, :find, @uri, nil) end @@ -42,7 +42,7 @@ describe Puppet::Indirector::FileMetadata::File do end it "should collect the attributes of the instances returned" do - FileTest.expects(:exists?).with(@path).returns true + Puppet::FileSystem::File.expects(:exist?).with(@path).returns true @metadata.expects(:path2instances).returns( [mock("one", :collect => nil), mock("two", :collect => nil)] ) @metadata.search(@request) end diff --git a/spec/unit/indirector/file_server_spec.rb b/spec/unit/indirector/file_server_spec.rb index 00649875f..6f491b065 100755 --- a/spec/unit/indirector/file_server_spec.rb +++ b/spec/unit/indirector/file_server_spec.rb @@ -154,7 +154,7 @@ describe Puppet::Indirector::FileServer do @mount.expects(:search).with { |key, request| key == "rel/path" }.returns %w{/one /two} - FileTest.stubs(:exist?).returns true + Puppet::FileSystem::File.stubs(:exist?).returns true one = mock 'fileset_one' Puppet::FileServing::Fileset.expects(:new).with("/one", @request).returns(one) @@ -171,7 +171,7 @@ describe Puppet::Indirector::FileServer do @mount.expects(:search).with { |key, request| key == "rel/path" }.returns [] - FileTest.stubs(:exist?).returns true + Puppet::FileSystem::File.stubs(:exist?).returns true Puppet::FileServing::Fileset.expects(:merge).returns("one" => "/one", "two" => "/two") @@ -193,7 +193,7 @@ describe Puppet::Indirector::FileServer do @mount.expects(:search).with { |key, request| key == "rel/path" }.returns [] - FileTest.stubs(:exist?).returns true + Puppet::FileSystem::File.stubs(:exist?).returns true Puppet::FileServing::Fileset.expects(:merge).returns("one" => "/one") @@ -211,7 +211,7 @@ describe Puppet::Indirector::FileServer do @mount.expects(:search).with { |key, options| key == "rel/path" }.returns [] - FileTest.stubs(:exist?).returns true + Puppet::FileSystem::File.stubs(:exist?).returns true Puppet::FileServing::Fileset.expects(:merge).returns("one" => "/one") diff --git a/spec/unit/indirector/hiera_spec.rb b/spec/unit/indirector/hiera_spec.rb deleted file mode 100644 index 36b598c37..000000000 --- a/spec/unit/indirector/hiera_spec.rb +++ /dev/null @@ -1,154 +0,0 @@ -require 'spec_helper' -require 'puppet/indirector/hiera' -require 'hiera/backend' - -describe Puppet::Indirector::Hiera do - include PuppetSpec::Files - - def write_hiera_config(config_file, datadir) - File.open(config_file, 'w') do |f| - f.write("--- - :yaml: - :datadir: #{datadir} - :hierarchy: ['global'] - :logger: 'noop' - :backends: ['yaml'] - ") - end - end - - before do - Puppet.settings[:hiera_config] = hiera_config_file - write_hiera_config(hiera_config_file, datadir) - - Puppet::Indirector::Terminus.stubs(:register_terminus_class) - Puppet::Indirector::Indirection.stubs(:instance).returns(indirection) - - module Testing; end - @hiera_class = class Testing::Hiera < Puppet::Indirector::Hiera - self - end - end - - let(:model) { mock('model') } - let(:options) { {:host => 'foo' } } - - let(:request_integer) do - stub('request', :key => "integer", :options => options) - end - - let(:request_string) do - stub('request', :key => "string", :options => options) - end - - let(:request_array) do - stub('request', :key => "array", :options => options) - end - - let(:request_hash) do - stub('request', :key => "hash", :options => options) - end - - let(:indirection) do - stub('indirection', :name => :none, :register_terminus_type => nil, - :model => model) - end - - let(:facts) do - { 'fqdn' => 'agent.testing.com' } - end - let(:facter_obj) { stub(:values => facts) } - - let(:hiera_config_file) do - tmpfile("hiera.yaml") - end - - let(:datadir) { my_fixture_dir } - - it "should be the default data_binding terminus" do - Puppet.settings[:data_binding_terminus].should == :hiera - end - - it "should raise an error if we don't have the hiera feature" do - Puppet.features.expects(:hiera?).returns(false) - lambda { @hiera_class.new }.should raise_error RuntimeError, - "Hiera terminus not supported without hiera library" - end - - describe "the behavior of the hiera_config method", :if => Puppet.features.hiera? do - let(:default_hiera_config) do - { - :logger => "puppet", - :backends => ["yaml"], - :yaml => { :datadir => datadir }, - :hierarchy => ["global"] - } - end - - it "should load the hiera config file by delegating to Hiera" do - Hiera::Config.expects(:load).with(hiera_config_file).returns({}) - @hiera_class.hiera_config - end - - it "should override the logger and set it to puppet" do - @hiera_class.hiera_config[:logger].should == "puppet" - end - - it "should return a hiera configuration hash" do - results = @hiera_class.hiera_config - default_hiera_config.each do |key,value| - results[key].should == value - end - results.should be_a_kind_of Hash - end - - context "when the Hiera configuration file does not exist" do - let(:path) { File.expand_path('/doesnotexist') } - - before do - Puppet.settings[:hiera_config] = path - end - - it "should log a warning" do - Puppet.expects(:warning).with( - "Config file #{path} not found, using Hiera defaults") - @hiera_class.hiera_config - end - - it "should only configure the logger and set it to puppet" do - Puppet.expects(:warning).with( - "Config file #{path} not found, using Hiera defaults") - @hiera_class.hiera_config.should == { :logger => 'puppet' } - end - end - end - - describe "the behavior of the find method", :if => Puppet.features.hiera? do - - let(:data_binder) { @hiera_class.new } - - it "should support looking up an integer" do - data_binder.find(request_integer).should == 3000 - end - - it "should support looking up a string" do - data_binder.find(request_string).should == 'apache' - end - - it "should support looking up an array" do - data_binder.find(request_array).should == [ - '0.ntp.puppetlabs.com', - '1.ntp.puppetlabs.com', - ] - end - - it "should support looking up a hash" do - data_binder.find(request_hash).should == { - 'user' => 'Hightower', - 'group' => 'admin', - 'mode' => '0644' - } - end - end -end - diff --git a/spec/unit/indirector/json_spec.rb b/spec/unit/indirector/json_spec.rb index c80bcd33c..664a32749 100755 --- a/spec/unit/indirector/json_spec.rb +++ b/spec/unit/indirector/json_spec.rb @@ -131,20 +131,20 @@ describe Puppet::Indirector::JSON do with_content('hello') do subject.destroy(request) end - File.should_not be_exist file + Puppet::FileSystem::File.exist?(file).should be_false end it "silently succeeds when files don't exist" do - File.unlink(file) rescue nil + Puppet::FileSystem::File.unlink(file) rescue nil subject.destroy(request).should be_true end it "raises an informative error for other failures" do - File.stubs(:unlink).with(file).raises(Errno::EPERM, 'fake permission problem') + Puppet::FileSystem::File.stubs(:unlink).with(file).raises(Errno::EPERM, 'fake permission problem') with_content('hello') do expect { subject.destroy(request) }.to raise_error Puppet::Error end - File.unstub(:unlink) # thanks, mocha + Puppet::FileSystem::File.unstub(:unlink) # thanks, mocha end end end diff --git a/spec/unit/indirector/key/file_spec.rb b/spec/unit/indirector/key/file_spec.rb index 57be73691..95c212523 100755 --- a/spec/unit/indirector/key/file_spec.rb +++ b/spec/unit/indirector/key/file_spec.rb @@ -64,33 +64,32 @@ describe Puppet::SSL::Key::File do end it "should save the public key when saving the private key" do - Puppet.settings.stubs(:writesub) + fh = StringIO.new - fh = mock 'filehandle' - - Puppet.settings.expects(:writesub).with(:publickeydir, @public_key_path).yields fh + Puppet.settings.setting(:publickeydir).expects(:open_file).with(@public_key_path, 'w').yields fh + Puppet.settings.setting(:privatekeydir).stubs(:open_file) @public_key.expects(:to_pem).returns "my pem" - fh.expects(:print).with "my pem" - @searcher.save(@request) + + expect(fh.string).to eq("my pem") end it "should destroy the public key when destroying the private key" do - File.stubs(:unlink).with(@private_key_path) - FileTest.stubs(:exist?).with(@private_key_path).returns true - FileTest.expects(:exist?).with(@public_key_path).returns true - File.expects(:unlink).with(@public_key_path) + Puppet::FileSystem::File.stubs(:unlink).with(@private_key_path) + Puppet::FileSystem::File.stubs(:exist?).with(@private_key_path).returns true + Puppet::FileSystem::File.expects(:exist?).with(@public_key_path).returns true + Puppet::FileSystem::File.expects(:unlink).with(@public_key_path) @searcher.destroy(@request) end it "should not fail if the public key does not exist when deleting the private key" do - File.stubs(:unlink).with(@private_key_path) + Puppet::FileSystem::File.stubs(:unlink).with(@private_key_path) - FileTest.stubs(:exist?).with(@private_key_path).returns true - FileTest.expects(:exist?).with(@public_key_path).returns false - File.expects(:unlink).with(@public_key_path).never + Puppet::FileSystem::File.stubs(:exist?).with(@private_key_path).returns true + Puppet::FileSystem::File.expects(:exist?).with(@public_key_path).returns false + Puppet::FileSystem::File.expects(:unlink).with(@public_key_path).never @searcher.destroy(@request) end diff --git a/spec/unit/indirector/resource/ral_spec.rb b/spec/unit/indirector/resource/ral_spec.rb index 0e14a322b..8b42f6881 100755 --- a/spec/unit/indirector/resource/ral_spec.rb +++ b/spec/unit/indirector/resource/ral_spec.rb @@ -2,6 +2,13 @@ require 'spec_helper' describe "Puppet::Resource::Ral" do + + it "is deprecated on the network, but still allows requests" do + Puppet.expects(:deprecation_warning) + + expect(Puppet::Resource::Ral.new.allow_remote_requests?).to eq(true) + end + describe "find" do before do @request = stub 'request', :key => "user/root" diff --git a/spec/unit/indirector/resource/store_configs_spec.rb b/spec/unit/indirector/resource/store_configs_spec.rb index aab2a4475..0ddb94470 100755 --- a/spec/unit/indirector/resource/store_configs_spec.rb +++ b/spec/unit/indirector/resource/store_configs_spec.rb @@ -9,4 +9,15 @@ end describe Puppet::Resource::StoreConfigs do it_should_behave_like "a StoreConfigs terminus" + + before :each do + Puppet[:storeconfigs] = true + Puppet[:storeconfigs_backend] = "store_configs_testing" + end + + it "is deprecated on the network, but still allows requests" do + Puppet.expects(:deprecation_warning) + + expect(Puppet::Resource::StoreConfigs.new.allow_remote_requests?).to eq(true) + end end diff --git a/spec/unit/indirector/rest_spec.rb b/spec/unit/indirector/rest_spec.rb index 787c4cb5e..0bf0bcb48 100755 --- a/spec/unit/indirector/rest_spec.rb +++ b/spec/unit/indirector/rest_spec.rb @@ -109,6 +109,10 @@ describe Puppet::Indirector::REST do string.split(',').collect { |s| convert_from(format, s) } end + def to_data_hash + { 'name' => @name, 'data' => @data } + end + def ==(other) other.is_a? Puppet::TestModel and other.name == name and other.data == data end @@ -205,21 +209,21 @@ describe Puppet::Indirector::REST do @request = stub 'request', :key => "foo", :server => nil, :port => nil terminus.class.expects(:port).returns 321 terminus.class.expects(:server).returns "myserver" - Puppet::Network::HTTP::Connection.expects(:new).with("myserver", 321).returns "myconn" + Puppet::Network::HttpPool.expects(:http_instance).with("myserver", 321).returns "myconn" terminus.network(@request).should == "myconn" end it "should use the server from the indirection request if one is present" do @request = stub 'request', :key => "foo", :server => "myserver", :port => nil terminus.class.stubs(:port).returns 321 - Puppet::Network::HTTP::Connection.expects(:new).with("myserver", 321).returns "myconn" + Puppet::Network::HttpPool.expects(:http_instance).with("myserver", 321).returns "myconn" terminus.network(@request).should == "myconn" end it "should use the port from the indirection request if one is present" do @request = stub 'request', :key => "foo", :server => nil, :port => 321 terminus.class.stubs(:server).returns "myserver" - Puppet::Network::HTTP::Connection.expects(:new).with("myserver", 321).returns "myconn" + Puppet::Network::HttpPool.expects(:http_instance).with("myserver", 321).returns "myconn" terminus.network(@request).should == "myconn" end end diff --git a/spec/unit/indirector/ssl_file_spec.rb b/spec/unit/indirector/ssl_file_spec.rb index 4cc0e216f..8406cc9d7 100755 --- a/spec/unit/indirector/ssl_file_spec.rb +++ b/spec/unit/indirector/ssl_file_spec.rb @@ -121,9 +121,9 @@ describe Puppet::Indirector::SslFile do describe "when finding certificates on disk" do describe "and no certificate is present" do it "should return nil" do - FileTest.expects(:exist?).with(@path).returns(true) + Puppet::FileSystem::File.expects(:exist?).with(@path).returns(true) Dir.expects(:entries).with(@path).returns([]) - FileTest.expects(:exist?).with(@certpath).returns(false) + Puppet::FileSystem::File.expects(:exist?).with(@certpath).returns(false) @searcher.find(@request).should be_nil end @@ -139,7 +139,7 @@ describe Puppet::Indirector::SslFile do context "is readable" do it "should return an instance of the model, which it should use to read the certificate" do - FileTest.expects(:exist?).with(@certpath).returns true + Puppet::FileSystem::File.expects(:exist?).with(@certpath).returns true model.expects(:new).with("myname").returns cert cert.expects(:read).with(@certpath) @@ -150,7 +150,7 @@ describe Puppet::Indirector::SslFile do context "is unreadable" do it "should raise an exception" do - FileTest.expects(:exist?).with(@certpath).returns(true) + Puppet::FileSystem::File.expects(:exist?).with(@certpath).returns(true) model.expects(:new).with("myname").returns cert cert.expects(:read).with(@certpath).raises(Errno::EACCES) @@ -171,9 +171,9 @@ describe Puppet::Indirector::SslFile do # 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") - FileTest.expects(:exist?).with(@path).returns(false) + Puppet::FileSystem::File.expects(:exist?).with(@path).returns(false) dir, file = File.split(@path) - FileTest.expects(:exist?).with(dir).returns true + Puppet::FileSystem::File.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) @@ -221,7 +221,7 @@ describe Puppet::Indirector::SslFile do @searcher.class.store_in @setting fh = mock 'filehandle' fh.stubs :print - Puppet.settings.expects(:writesub).with(@setting, @certpath).yields fh + Puppet.settings.setting(@setting).expects(:open_file).with(@certpath, 'w').yields fh @searcher.save(@request) end @@ -233,7 +233,7 @@ describe Puppet::Indirector::SslFile do fh = mock 'filehandle' fh.stubs :print - Puppet.settings.expects(:write).with(@setting).yields fh + Puppet.settings.setting(@setting).expects(:open).with('w').yields fh @searcher.save(@request) end end @@ -246,7 +246,7 @@ describe Puppet::Indirector::SslFile do fh = mock 'filehandle' fh.stubs :print - Puppet.settings.expects(:write).with(:cakey).yields fh + Puppet.settings.setting(:cakey).expects(:open).with('w').yields fh @searcher.stubs(:ca?).returns true @searcher.save(@request) end @@ -256,7 +256,7 @@ describe Puppet::Indirector::SslFile do describe "when destroying certificates" do describe "that do not exist" do before do - FileTest.expects(:exist?).with(@certpath).returns false + Puppet::FileSystem::File.expects(:exist?).with(@certpath).returns false end it "should return false" do @@ -265,18 +265,15 @@ describe Puppet::Indirector::SslFile do end describe "that exist" do - before do - FileTest.expects(:exist?).with(@certpath).returns true - end - it "should unlink the certificate file" do - File.expects(:unlink).with(@certpath) + Puppet::FileSystem::File.expects(:exist?).with(@certpath).returns true + Puppet::FileSystem::File.expects(:unlink).with(@certpath) @searcher.destroy(@request) end it "should log that is removing the file" do - File.stubs(:exist?).returns true - File.stubs(:unlink) + Puppet::FileSystem::File.stubs(:exist?).returns true + Puppet::FileSystem::File.stubs(:unlink) Puppet.expects(:notice) @searcher.destroy(@request) end diff --git a/spec/unit/indirector/yaml_spec.rb b/spec/unit/indirector/yaml_spec.rb index 8a2753479..6830869b1 100755 --- a/spec/unit/indirector/yaml_spec.rb +++ b/spec/unit/indirector/yaml_spec.rb @@ -149,15 +149,15 @@ describe Puppet::Indirector::Yaml do end it "should unlink the right yaml file if it exists" do - File.expects(:exists?).with(path).returns true - File.expects(:unlink).with(path) + Puppet::FileSystem::File.expects(:exist?).with(path).returns true + Puppet::FileSystem::File.expects(:unlink).with(path) @store.destroy(@request) end it "should not unlink the yaml file if it does not exists" do - File.expects(:exists?).with(path).returns false - File.expects(:unlink).with(path).never + Puppet::FileSystem::File.expects(:exist?).with(path).returns false + Puppet::FileSystem::File.expects(:unlink).with(path).never @store.destroy(@request) end diff --git a/spec/unit/module_spec.rb b/spec/unit/module_spec.rb index c06f278eb..5c4065e14 100755 --- a/spec/unit/module_spec.rb +++ b/spec/unit/module_spec.rb @@ -15,7 +15,7 @@ describe Puppet::Module do before do # This is necessary because of the extra checks we have for the deprecated # 'plugins' directory - FileTest.stubs(:exist?).returns false + Puppet::FileSystem::File.stubs(:exist?).returns false end it "should have a class method that returns a named module from a given environment" do @@ -90,12 +90,14 @@ describe Puppet::Module do describe "when finding unmet dependencies" do before do - FileTest.unstub(:exist?) + Puppet::FileSystem::File.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::File.expects(:exist?).twice.with(metadata_file).returns true mod = PuppetSpec::Modules.create( 'needy', @modpath, @@ -116,6 +118,8 @@ describe Puppet::Module do end it "should list modules that are missing and have invalid names" do + metadata_file = "#{@modpath}/needy/metadata.json" + Puppet::FileSystem::File.expects(:exist?).with(metadata_file).twice.returns true mod = PuppetSpec::Modules.create( 'needy', @modpath, @@ -136,6 +140,10 @@ describe Puppet::Module do end it "should list modules with unmet version requirement" do + ['foobar', 'foobaz'].each do |mod_name| + metadata_file = "#{@modpath}/#{mod_name}/metadata.json" + Puppet::FileSystem::File.stubs(:exist?).with(metadata_file).returns true + end mod = PuppetSpec::Modules.create( 'foobar', @modpath, @@ -204,6 +212,8 @@ describe Puppet::Module do end it "should consider a dependency without a semantic version to be unmet" do + metadata_file = "#{@modpath}/foobar/metadata.json" + Puppet::FileSystem::File.expects(:exist?).with(metadata_file).times(3).returns true mod = PuppetSpec::Modules.create( 'foobar', @modpath, @@ -244,6 +254,10 @@ describe Puppet::Module do end it "should only list unmet dependencies" do + [name, 'satisfied'].each do |mod_name| + metadata_file = "#{@modpath}/#{mod_name}/metadata.json" + Puppet::FileSystem::File.expects(:exist?).with(metadata_file).twice.returns true + end mod = PuppetSpec::Modules.create( name, @modpath, @@ -357,35 +371,42 @@ describe Puppet::Module do end end - [:plugins, :templates, :files, :manifests].each do |filetype| - dirname = filetype == :plugins ? "lib" : filetype.to_s + [: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") - FileTest.expects(:exist?).with(module_file).returns true + Puppet::FileSystem::File.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) - FileTest.expects(:exist?).with(module_file).returns true + Puppet::FileSystem::File.expects(:exist?).with(module_file).returns true mod.send(filetype.to_s + "?").should be_true end it "should consider #{filetype} to be absent if their base directory does not exist" do module_file = File.join(path, dirname) - FileTest.expects(:exist?).with(module_file).returns false + Puppet::FileSystem::File.expects(:exist?).with(module_file).returns false mod.send(filetype.to_s + "?").should be_false end it "should return nil if asked to return individual #{filetype} that don't exist" do module_file = File.join(path, dirname, "my/file") - FileTest.expects(:exist?).with(module_file).returns false + Puppet::FileSystem::File.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) - FileTest.expects(:exist?).with(base).returns true + Puppet::FileSystem::File.expects(:exist?).with(base).returns true mod.send(filetype.to_s.sub(/s$/, ''), nil).should == base end end @@ -418,8 +439,8 @@ describe Puppet::Module, "when finding matching manifests" do end it "should default to the 'init' file if no glob pattern is specified" do - FileTest.expects(:exist?).with("/a/manifests/init.pp").returns(true) - FileTest.expects(:exist?).with("/a/manifests/init.rb").returns(false) + Puppet::FileSystem::File.expects(:exist?).with("/a/manifests/init.pp").returns(true) + Puppet::FileSystem::File.expects(:exist?).with("/a/manifests/init.rb").returns(false) @mod.match_manifests(nil).should == %w{/a/manifests/init.pp} end @@ -471,21 +492,21 @@ describe Puppet::Module do end it "should have metadata if it has a metadata file and its data is not empty" do - FileTest.expects(:exist?).with(@module.metadata_file).returns true + Puppet::FileSystem::File.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 - FileTest.expects(:exist?).with(@module.metadata_file).returns true + Puppet::FileSystem::File.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 - FileTest.expects(:exist?).with(@module.metadata_file).returns true + Puppet::FileSystem::File.expects(:exist?).with(@module.metadata_file).returns true File.stubs(:read).with(@module.metadata_file).returns "/* +-----------------------------------------------------------------------+ | | @@ -503,7 +524,7 @@ describe Puppet::Module do end it "should know if it is missing a metadata file" do - FileTest.expects(:exist?).with(@module.metadata_file).returns false + Puppet::FileSystem::File.expects(:exist?).with(@module.metadata_file).returns false @module.should_not be_has_metadata end @@ -519,6 +540,13 @@ describe Puppet::Module do Puppet::Module.new("yay", "/path", mock("env")) end + it "should tolerate failure to parse" do + Puppet::FileSystem::File.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 + end + def a_module_with_metadata(data) text = data.to_pson diff --git a/spec/unit/module_tool/tar/gnu_spec.rb b/spec/unit/module_tool/tar/gnu_spec.rb index b5fc78918..93245da1a 100644 --- a/spec/unit/module_tool/tar/gnu_spec.rb +++ b/spec/unit/module_tool/tar/gnu_spec.rb @@ -8,9 +8,9 @@ describe Puppet::ModuleTool::Tar::Gnu do let(:destfile) { '/the/dest/file.tar.gz' } it "unpacks a tar file" do - Puppet::Util::Execution.expects(:execute).with("tar xzf #{sourcefile} --no-same-permissions --no-same-owner -C #{destdir}") + Puppet::Util::Execution.expects(:execute).with("tar xzf #{sourcefile} --no-same-owner -C #{destdir}") Puppet::Util::Execution.expects(:execute).with("find #{destdir} -type d -exec chmod 755 {} +") - Puppet::Util::Execution.expects(:execute).with("find #{destdir} -type f -exec chmod 644 {} +") + Puppet::Util::Execution.expects(:execute).with("find #{destdir} -type f -exec chmod a-wst {} +") Puppet::Util::Execution.expects(:execute).with("chown -R <owner:group> #{destdir}") subject.unpack(sourcefile, destdir, '<owner:group>') end diff --git a/spec/unit/module_tool/tar/solaris_spec.rb b/spec/unit/module_tool/tar/solaris_spec.rb index 23bbd8d20..1ca7a6a09 100644 --- a/spec/unit/module_tool/tar/solaris_spec.rb +++ b/spec/unit/module_tool/tar/solaris_spec.rb @@ -8,9 +8,9 @@ describe Puppet::ModuleTool::Tar::Solaris do let(:destfile) { '/the/dest/file.tar.gz' } it "unpacks a tar file" do - Puppet::Util::Execution.expects(:execute).with("gtar xzf #{sourcefile} --no-same-permissions --no-same-owner -C #{destdir}") + Puppet::Util::Execution.expects(:execute).with("gtar xzf #{sourcefile} --no-same-owner -C #{destdir}") Puppet::Util::Execution.expects(:execute).with("find #{destdir} -type d -exec chmod 755 {} +") - Puppet::Util::Execution.expects(:execute).with("find #{destdir} -type f -exec chmod 644 {} +") + Puppet::Util::Execution.expects(:execute).with("find #{destdir} -type f -exec chmod a-wst {} +") Puppet::Util::Execution.expects(:execute).with("chown -R <owner:group> #{destdir}") subject.unpack(sourcefile, destdir, '<owner:group>') end diff --git a/spec/unit/module_tool/tar_spec.rb b/spec/unit/module_tool/tar_spec.rb new file mode 100644 index 000000000..3de0dda48 --- /dev/null +++ b/spec/unit/module_tool/tar_spec.rb @@ -0,0 +1,45 @@ +require 'spec_helper' +require 'puppet/module_tool/tar' + +describe Puppet::ModuleTool::Tar do + + it "uses gtar when present on Solaris" do + Facter.stubs(:value).with('osfamily').returns 'Solaris' + Puppet::Util.stubs(:which).with('gtar').returns '/usr/bin/gtar' + + described_class.instance(nil).should be_a_kind_of Puppet::ModuleTool::Tar::Solaris + end + + it "uses gtar when present on OpenBSD" do + Facter.stubs(:value).with('osfamily').returns 'OpenBSD' + Puppet::Util.stubs(:which).with('gtar').returns '/usr/bin/gtar' + + described_class.instance(nil).should be_a_kind_of Puppet::ModuleTool::Tar::Solaris + end + + it "uses tar when present and not on Windows" do + Facter.stubs(:value).with('osfamily').returns 'ObscureLinuxDistro' + Puppet::Util.stubs(:which).with('tar').returns '/usr/bin/tar' + Puppet::Util::Platform.stubs(:windows?).returns false + + described_class.instance(nil).should be_a_kind_of Puppet::ModuleTool::Tar::Gnu + end + + it "falls back to minitar when it and zlib are present" do + Facter.stubs(:value).with('osfamily').returns 'Windows' + Puppet::Util.stubs(:which).with('tar') + Puppet::Util::Platform.stubs(:windows?).returns true + Puppet.stubs(:features).returns(stub(:minitar? => true, :zlib? => true)) + + described_class.instance(nil).should be_a_kind_of Puppet::ModuleTool::Tar::Mini + end + + it "fails when there is no possible implementation" do + Facter.stubs(:value).with('osfamily').returns 'Windows' + Puppet::Util.stubs(:which).with('tar') + Puppet::Util::Platform.stubs(:windows?).returns true + Puppet.stubs(:features).returns(stub(:minitar? => false, :zlib? => false)) + + expect { described_class.instance(nil) }.to raise_error RuntimeError, /No suitable tar/ + end +end diff --git a/spec/unit/network/authconfig_spec.rb b/spec/unit/network/authconfig_spec.rb index 6814b2c1b..be45152c0 100755 --- a/spec/unit/network/authconfig_spec.rb +++ b/spec/unit/network/authconfig_spec.rb @@ -5,7 +5,8 @@ require 'puppet/network/authconfig' describe Puppet::Network::AuthConfig do before :each do - File.stubs(:stat).returns(stub('stat', :ctime => :now)) + stub_file = stub('file', :stat => stub('stat', :ctime => :now)) + Puppet::FileSystem::File.stubs(:new).returns stub_file Time.stubs(:now).returns Time.now Puppet::Network::AuthConfig.any_instance.stubs(:exists?).returns(true) diff --git a/spec/unit/network/authentication_spec.rb b/spec/unit/network/authentication_spec.rb index 5b54757b8..c18552ab8 100755 --- a/spec/unit/network/authentication_spec.rb +++ b/spec/unit/network/authentication_spec.rb @@ -20,7 +20,7 @@ describe Puppet::Network::Authentication do describe "when warning about upcoming expirations" do before do Puppet::SSL::CertificateAuthority.stubs(:ca?).returns(false) - FileTest.stubs(:exist?).returns(false) + Puppet::FileSystem::File.stubs(:exist?).returns(false) end it "should check the expiration of the CA certificate" do @@ -34,7 +34,7 @@ describe Puppet::Network::Authentication do it "should check the expiration of the localhost certificate" do Puppet::SSL::Host.stubs(:localhost).returns(host) cert.expects(:near_expiration?).returns(false) - FileTest.stubs(:exist?).with(Puppet[:hostcert]).returns(true) + Puppet::FileSystem::File.stubs(:exist?).with(Puppet[:hostcert]).returns(true) subject.warn_if_near_expiration end diff --git a/spec/unit/network/format_handler_spec.rb b/spec/unit/network/format_handler_spec.rb index b8ab39634..1716a7296 100755 --- a/spec/unit/network/format_handler_spec.rb +++ b/spec/unit/network/format_handler_spec.rb @@ -63,8 +63,8 @@ describe Puppet::Network::FormatHandler do Puppet::Network::FormatHandler.most_suitable_format_for(accepted, [:one, :two]) end - it "finds either format when anything is accepted" do - [format_one, format_two].should include(suitable_in_setup_formats(["*/*"])) + it "finds the most preferred format when anything is acceptable" do + Puppet::Network::FormatHandler.most_suitable_format_for(["*/*"], [:two, :one]).should == format_two end it "finds no format when none are acceptable" do diff --git a/spec/unit/network/formats_spec.rb b/spec/unit/network/formats_spec.rb index 8531a541a..57ff77795 100755 --- a/spec/unit/network/formats_spec.rb +++ b/spec/unit/network/formats_spec.rb @@ -26,6 +26,30 @@ class PsonTest end describe "Puppet Network Format" do + it "should include a msgpack format", :if => Puppet.features.msgpack? do + Puppet::Network::FormatHandler.format(:msgpack).should_not be_nil + end + + describe "msgpack", :if => Puppet.features.msgpack? do + before do + @msgpack = Puppet::Network::FormatHandler.format(:msgpack) + end + + it "should have its mime type set to application/x-msgpack" do + @msgpack.mime.should == "application/x-msgpack" + end + + it "should have a weight of 20" do + @msgpack.weight.should == 20 + end + + it "should fail when one element does not have a from_pson" do + expect do + @msgpack.intern_multiple(Hash, MessagePack.pack(["foo"])) + end.to raise_error(NoMethodError) + end + end + it "should include a yaml format" do Puppet::Network::FormatHandler.format(:yaml).should_not be_nil end diff --git a/spec/unit/network/http/connection_spec.rb b/spec/unit/network/http/connection_spec.rb index 0d6da0671..467705f01 100644 --- a/spec/unit/network/http/connection_spec.rb +++ b/spec/unit/network/http/connection_spec.rb @@ -7,19 +7,13 @@ describe Puppet::Network::HTTP::Connection do let (:host) { "me" } let (:port) { 54321 } - subject { Puppet::Network::HTTP::Connection.new(host, port) } + subject { Puppet::Network::HTTP::Connection.new(host, port, :verify => Puppet::SSL::Validator.no_validator) } context "when providing HTTP connections" do after do Puppet::Network::HTTP::Connection.instance_variable_set("@ssl_host", nil) end - it "should use the global SSL::Host instance to get its certificate information" do - host = mock 'host' - Puppet::SSL::Host.expects(:localhost).with.returns host - subject.send(:ssl_host).should equal(host) - end - context "when initializing http instances" do before :each do # All of the cert stuff is tested elsewhere @@ -39,51 +33,12 @@ describe Puppet::Network::HTTP::Connection do end it "can set ssl using an option" do - Puppet::Network::HTTP::Connection.new(host, port, :use_ssl => false).send(:connection).should_not be_use_ssl - Puppet::Network::HTTP::Connection.new(host, port, :use_ssl => true).send(:connection).should be_use_ssl - end - - describe "peer verification" do - def setup_standard_ssl_configuration - ca_cert_file = File.expand_path('/path/to/ssl/certs/ca_cert.pem') - FileTest.stubs(:exist?).with(ca_cert_file).returns(true) - - ssl_configuration = stub('ssl_configuration', :ca_auth_file => ca_cert_file) - Puppet::Network::HTTP::Connection.any_instance.stubs(:ssl_configuration).returns(ssl_configuration) - end - - def setup_standard_hostcert - host_cert_file = File.expand_path('/path/to/ssl/certs/host_cert.pem') - FileTest.stubs(:exist?).with(host_cert_file).returns(true) - - Puppet[:hostcert] = host_cert_file - end - - def setup_standard_ssl_host - cert = stub('cert', :content => 'real_cert') - key = stub('key', :content => 'real_key') - host = stub('host', :certificate => cert, :key => key, :ssl_store => stub('store')) - - Puppet::Network::HTTP::Connection.any_instance.stubs(:ssl_host).returns(host) - end - - before do - setup_standard_ssl_configuration - setup_standard_hostcert - setup_standard_ssl_host - end - - it "can enable peer verification" do - Puppet::Network::HTTP::Connection.new(host, port, :verify_peer => true).send(:connection).verify_mode.should == OpenSSL::SSL::VERIFY_PEER - end - - it "can disable peer verification" do - Puppet::Network::HTTP::Connection.new(host, port, :verify_peer => false).send(:connection).verify_mode.should == OpenSSL::SSL::VERIFY_NONE - end + Puppet::Network::HTTP::Connection.new(host, port, :use_ssl => false, :verify => Puppet::SSL::Validator.no_validator).send(:connection).should_not be_use_ssl + Puppet::Network::HTTP::Connection.new(host, port, :use_ssl => true, :verify => Puppet::SSL::Validator.no_validator).send(:connection).should be_use_ssl end context "proxy and timeout settings should propagate" do - subject { Puppet::Network::HTTP::Connection.new(host, port).send(:connection) } + subject { Puppet::Network::HTTP::Connection.new(host, port, :verify => Puppet::SSL::Validator.no_validator).send(:connection) } before :each do Puppet[:http_proxy_host] = "myhost" Puppet[:http_proxy_port] = 432 @@ -104,94 +59,13 @@ describe Puppet::Network::HTTP::Connection do it "should raise Puppet::Error when invalid options are specified" do expect { Puppet::Network::HTTP::Connection.new(host, port, :invalid_option => nil) }.to raise_error(Puppet::Error, 'Unrecognized option(s): :invalid_option') end - - end - - describe "when doing SSL setup for http instances" do - let :store do stub('store') end - - let :ca_auth_file do - '/path/to/ssl/certs/ssl_server_ca_auth.pem' - end - - let :ssl_configuration do - stub('ssl_configuration', :ca_auth_file => ca_auth_file) - end - - before :each do - Puppet[:hostcert] = '/host/cert' - Puppet::Network::HTTP::Connection.any_instance.stubs(:ssl_configuration).returns(ssl_configuration) - - cert = stub 'cert', :content => 'real_cert' - key = stub 'key', :content => 'real_key' - host = stub 'host', :certificate => cert, :key => key, :ssl_store => store - Puppet::Network::HTTP::Connection.any_instance.stubs(:ssl_host).returns(host) - end - - shared_examples "HTTPS setup without all certificates" do - subject { Puppet::Network::HTTP::Connection.new(host, port, :use_ssl => true).send(:connection) } - - it { should be_use_ssl } - its(:cert) { should be_nil } - its(:ca_file) { should be_nil } - its(:key) { should be_nil } - its(:verify_mode) { should == OpenSSL::SSL::VERIFY_NONE } - end - - context "with neither a host cert or a local CA cert" do - before :each do - FileTest.stubs(:exist?).with(Puppet[:hostcert]).returns(false) - FileTest.stubs(:exist?).with(ca_auth_file).returns(false) - end - - include_examples "HTTPS setup without all certificates" - end - - context "with there is no host certificate" do - before :each do - FileTest.stubs(:exist?).with(Puppet[:hostcert]).returns(false) - FileTest.stubs(:exist?).with(ca_auth_file).returns(true) - end - - include_examples "HTTPS setup without all certificates" - end - - context "with there is no local CA certificate" do - before :each do - FileTest.stubs(:exist?).with(Puppet[:hostcert]).returns(true) - FileTest.stubs(:exist?).with(ca_auth_file).returns(false) - end - - include_examples "HTTPS setup without all certificates" - end - - context "with both the host and CA cert" do - subject { Puppet::Network::HTTP::Connection.new(host, port, :use_ssl => true).send(:connection) } - - before :each do - FileTest.expects(:exist?).with(Puppet[:hostcert]).returns(true) - FileTest.expects(:exist?).with(ca_auth_file).returns(true) - end - - it { should be_use_ssl } - its(:cert_store) { should equal store } - its(:cert) { should == "real_cert" } - its(:key) { should == "real_key" } - its(:verify_mode) { should == OpenSSL::SSL::VERIFY_PEER } - its(:ca_file) { should == ca_auth_file } - end - - it "should set up certificate information when creating http instances" do - subject.expects(:cert_setup) - subject.send(:connection) - end end end context "when methods that accept a block are called with a block" do let (:host) { "my_server" } let (:port) { 8140 } - let (:subject) { Puppet::Network::HTTP::Connection.new(host, port, :use_ssl => false) } + let (:subject) { Puppet::Network::HTTP::Connection.new(host, port, :use_ssl => false, :verify => Puppet::SSL::Validator.no_validator) } let (:httpok) { Net::HTTPOK.new('1.1', 200, '') } before :each do @@ -228,95 +102,99 @@ describe Puppet::Network::HTTP::Connection do let (:host) { "my_server" } let (:port) { 8140 } let (:httpok) { Net::HTTPOK.new('1.1', 200, '') } - let (:subject) { Puppet::Network::HTTP::Connection.new(host, port) } - - def a_connection_that_verifies(args) - connection = Net::HTTP.new(host, port) - connection.stubs(:warn_if_near_expiration) - connection.stubs(:get).with do - connection.verify_callback.call(args[:has_passed_pre_checks], args[:in_context]) - true - end.raises(OpenSSL::SSL::SSLError.new(args[:fails_with])) - connection - end - - def a_store_context(args) - Puppet[:confdir] = tmpdir('conf') - ssl_context = mock('OpenSSL::X509::StoreContext') - if args[:verify_raises] - ssl_context.stubs(:current_cert).raises("oh noes") - else - cert = Puppet::SSL::CertificateAuthority.new.generate(args[:for_server], :dns_alt_names => args[:for_aliases]).content - ssl_context.stubs(:current_cert).returns(cert) - end - ssl_context.stubs(:chain).returns([]) - ssl_context.stubs(:error_string).returns(args[:with_error_string]) - ssl_context - end it "should provide a useful error message when one is available and certificate validation fails", :unless => Puppet.features.microsoft_windows? do - subject.stubs(:create_connection). - returns(a_connection_that_verifies(:has_passed_pre_checks => false, - :in_context => a_store_context(:for_server => 'not_my_server', - :with_error_string => 'shady looking signature'), - :fails_with => 'certificate verify failed')) - expect do - subject.request(:get, stub('request')) - end.to raise_error(Puppet::Error, "certificate verify failed: [shady looking signature for /CN=not_my_server]") - end + connection = Puppet::Network::HTTP::Connection.new( + host, port, + :verify => ConstantErrorValidator.new(:fails_with => 'certificate verify failed', + :error_string => 'shady looking signature')) - it "should provide a useful error message when verify_callback raises", :unless => Puppet.features.microsoft_windows? do - subject.stubs(:create_connection). - returns(a_connection_that_verifies(:has_passed_pre_checks => false, - :in_context => a_store_context(:verify_raises => true), - :fails_with => 'certificate verify failed')) expect do - subject.request(:get, stub('request')) - end.to raise_error(Puppet::Error, "certificate verify failed: [oh noes]") + connection.get('request') + end.to raise_error(Puppet::Error, "certificate verify failed: [shady looking signature]") end it "should provide a helpful error message when hostname was not match with server certificate", :unless => Puppet.features.microsoft_windows? do - subject.stubs(:create_connection). - returns(a_connection_that_verifies(:has_passed_pre_checks => true, - :in_context => a_store_context(:for_server => 'not_my_server', - :for_aliases => 'foo,bar,baz'), - :fails_with => 'hostname was not match with server certificate')) - - expect { subject.request(:get, stub('request')) }. - to raise_error(Puppet::Error) do |error| + Puppet[:confdir] = tmpdir('conf') + + connection = Puppet::Network::HTTP::Connection.new( + host, port, + :verify => ConstantErrorValidator.new( + :fails_with => 'hostname was not match with server certificate', + :peer_certs => [Puppet::SSL::CertificateAuthority.new.generate( + 'not_my_server', :dns_alt_names => 'foo,bar,baz')])) + + expect do + connection.get('request') + end.to raise_error(Puppet::Error) do |error| error.message =~ /Server hostname 'my_server' did not match server certificate; expected one of (.+)/ $1.split(', ').should =~ %w[DNS:foo DNS:bar DNS:baz DNS:not_my_server not_my_server] end end it "should pass along the error message otherwise" do - connection = Net::HTTP.new('my_server', 8140) - subject.stubs(:create_connection).returns(connection) - - connection.stubs(:get).raises(OpenSSL::SSL::SSLError.new('some other message')) + connection = Puppet::Network::HTTP::Connection.new( + host, port, + :verify => ConstantErrorValidator.new(:fails_with => 'some other message')) expect do - subject.request(:get, stub('request')) + connection.get('request') end.to raise_error(/some other message/) end it "should check all peer certificates for upcoming expiration", :unless => Puppet.features.microsoft_windows? do - connection = Net::HTTP.new('my_server', 8140) - subject.stubs(:create_connection).returns(connection) + Puppet[:confdir] = tmpdir('conf') + cert = Puppet::SSL::CertificateAuthority.new.generate( + 'server', :dns_alt_names => 'foo,bar,baz') + + connection = Puppet::Network::HTTP::Connection.new( + host, port, + :verify => NoProblemsValidator.new(cert)) - cert = stubs 'cert' - Puppet::SSL::Certificate.expects(:from_instance).twice.returns(cert) + Net::HTTP.any_instance.stubs(:get).returns(httpok) - connection.stubs(:get).with do - context = a_store_context(:for_server => 'a_server', :with_error_string => false) - connection.verify_callback.call(true, context) - connection.verify_callback.call(true, context) - true - end.returns(httpok) + connection.expects(:warn_if_near_expiration).with(cert) - subject.expects(:warn_if_near_expiration).with(cert, cert) + connection.get('request') + end + + class ConstantErrorValidator + def initialize(args) + @fails_with = args[:fails_with] + @error_string = args[:error_string] || "" + @peer_certs = args[:peer_certs] || [] + end - subject.request(:get, stubs('request')) + def setup_connection(connection) + connection.stubs(:get).with do + true + end.raises(OpenSSL::SSL::SSLError.new(@fails_with)) + end + + def peer_certs + @peer_certs + end + + def verify_errors + [@error_string] + end + end + + class NoProblemsValidator + def initialize(cert) + @cert = cert + end + + def setup_connection(connection) + end + + def peer_certs + [@cert] + end + + def verify_errors + [] + end end end @@ -324,7 +202,7 @@ describe Puppet::Network::HTTP::Connection do let (:other_host) { "redirected" } let (:other_port) { 9292 } let (:other_path) { "other-path" } - let (:subject) { Puppet::Network::HTTP::Connection.new("my_server", 8140, :use_ssl => false) } + let (:subject) { Puppet::Network::HTTP::Connection.new("my_server", 8140, :use_ssl => false, :verify => Puppet::SSL::Validator.no_validator) } let (:httpredirection) { Net::HTTPFound.new('1.1', 302, 'Moved Temporarily') } let (:httpok) { Net::HTTPOK.new('1.1', 200, '') } @@ -356,5 +234,4 @@ describe Puppet::Network::HTTP::Connection do }.to raise_error(Puppet::Network::HTTP::RedirectionLimitExceededException) end end - end diff --git a/spec/unit/network/http/handler_spec.rb b/spec/unit/network/http/handler_spec.rb index 0ff087b02..53da5be01 100755 --- a/spec/unit/network/http/handler_spec.rb +++ b/spec/unit/network/http/handler_spec.rb @@ -38,6 +38,9 @@ describe Puppet::Network::HTTP::Handler do end class Puppet::TestModel::Memory < Puppet::Indirector::Memory + def supports_remote_requests? + true + end end Puppet::TestModel.indirection.terminus_class = :memory @@ -150,9 +153,7 @@ describe Puppet::Network::HTTP::Handler do describe "when processing a request" do let(:response) do - obj = stub "http 200 ok" - obj.stubs(:[]=).with(Puppet::Network::HTTP::HEADER_PUPPET_VERSION, Puppet.version) - obj + { :status => 200 } end before do @@ -163,7 +164,6 @@ describe Puppet::Network::HTTP::Handler do it "should check the client certificate for upcoming expiration" do request = a_request cert = mock 'cert' - handler.stubs(:uri2indirection).returns(["facts", :mymethod, "key", {:node => "name"}]) handler.expects(:client_cert).returns(cert).with(request) handler.expects(:warn_if_near_expiration).with(cert) @@ -201,33 +201,31 @@ describe Puppet::Network::HTTP::Handler do handler.process(request, response) end - it "should call the 'do' method and delegate authorization to the authorization layer" do + it "should return 403 if the request is not authorized" do request = a_request handler.expects(:uri2indirection).returns(["facts", :mymethod, "key", {:node => "name"}]) - handler.expects(:do_mymethod).with("facts", "key", {:node => "name"}, request, response) + handler.expects(:do_mymethod).never + + handler.expects(:check_authorization).with("facts", :mymethod, "key", {:node => "name"}).raises(Puppet::Network::AuthorizationError.new("forbidden")) - handler.expects(:check_authorization).with("facts", :mymethod, "key", {:node => "name"}) + handler.expects(:set_response).with(anything, anything, 403) handler.process(request, response) end - it "should return 403 if the request is not authorized" do + it "should return an error code if the indirection does not support remote requests" do request = a_request - handler.expects(:uri2indirection).returns(["facts", :mymethod, "key", {:node => "name"}]) - handler.expects(:do_mymethod).never - - handler.expects(:check_authorization).with("facts", :mymethod, "key", {:node => "name"}).raises(Puppet::Network::AuthorizationError.new("forbidden")) - - handler.expects(:set_response).with(anything, anything, 403) + indirection.expects(:allow_remote_requests?).returns(false) handler.process(request, response) + + expect(response[:status]).to eq 404 end it "should serialize a controller exception when an exception is thrown while finding the model instance" do - request = a_request - handler.expects(:uri2indirection).returns(["facts", :find, "key", {:node => "name"}]) + request = a_request_that_finds(Puppet::TestModel.new("key")) handler.expects(:do_find).raises(ArgumentError, "The exception") handler.expects(:set_response).with(anything, "The exception", 400) @@ -302,7 +300,7 @@ describe Puppet::Network::HTTP::Handler do handler.expects(:set_response).with(response, data.render(:pson)) handler.expects(:set_content_type).with(response, Puppet::Network::FormatHandler.format(:pson)) - handler.do_find(indirection.name, "my data", {}, request, response) + handler.do_find(indirection, "my data", {}, request, response) end it "responds with a 406 error when no accept header is provided" do @@ -311,7 +309,7 @@ describe Puppet::Network::HTTP::Handler do request = a_request_that_finds(data, :accept_header => nil) expect do - handler.do_find(indirection.name, "my data", {}, request, response) + handler.do_find(indirection, "my data", {}, request, response) end.to raise_error(Puppet::Network::HTTP::Handler::HTTPNotAcceptableError) end @@ -321,7 +319,7 @@ describe Puppet::Network::HTTP::Handler do request = a_request_that_finds(data, :accept_header => "unknown, also/unknown") expect do - handler.do_find(indirection.name, "my data", {}, request, response) + handler.do_find(indirection, "my data", {}, request, response) end.to raise_error(Puppet::Network::HTTP::Handler::HTTPNotAcceptableError) end @@ -334,7 +332,7 @@ describe Puppet::Network::HTTP::Handler do handler.expects(:set_response).with(response, data_string) handler.expects(:set_content_type).with(response, Puppet::Network::FormatHandler.format(:pson)) - handler.do_find(indirection.name, "my data", {}, request, response) + handler.do_find(indirection, "my data", {}, request, response) end it "should return a 404 when no model instance can be found" do @@ -342,7 +340,7 @@ describe Puppet::Network::HTTP::Handler do request = a_request_that_finds(data, :accept_header => "unknown, pson, yaml") expect do - handler.do_find(indirection.name, "my data", {}, request, response) + handler.do_find(indirection, "my data", {}, request, response) end.to raise_error(Puppet::Network::HTTP::Handler::HTTPNotFoundError) end end @@ -377,7 +375,7 @@ describe Puppet::Network::HTTP::Handler do handler.expects(:set_response).with(response, Puppet::TestModel.render_multiple(:pson, [data])) handler.expects(:set_content_type).with(response, Puppet::Network::FormatHandler.format(:pson)) - handler.do_search(indirection.name, "my", {}, request, response) + handler.do_search(indirection, "my", {}, request, response) end it "should return [] when searching returns an empty array" do @@ -386,7 +384,7 @@ describe Puppet::Network::HTTP::Handler do handler.expects(:set_response).with(response, Puppet::TestModel.render_multiple(:pson, [])) handler.expects(:set_content_type).with(response, Puppet::Network::FormatHandler.format(:pson)) - handler.do_search(indirection.name, "nothing", {}, request, response) + handler.do_search(indirection, "nothing", {}, request, response) end it "should return a 404 when searching returns nil" do @@ -394,7 +392,7 @@ describe Puppet::Network::HTTP::Handler do indirection.expects(:search).returns(nil) expect do - handler.do_search(indirection.name, "nothing", {}, request, response) + handler.do_search(indirection, "nothing", {}, request, response) end.to raise_error(Puppet::Network::HTTP::Handler::HTTPNotFoundError) end end @@ -405,7 +403,7 @@ describe Puppet::Network::HTTP::Handler do indirection.save(data, "my data") request = a_request_that_destroys(data) - handler.do_destroy(indirection.name, "my data", {}, request, response) + handler.do_destroy(indirection, "my data", {}, request, response) Puppet::TestModel.indirection.find("my data").should be_nil end @@ -418,7 +416,7 @@ describe Puppet::Network::HTTP::Handler do handler.expects(:set_response).with(response, data.render(:yaml)) handler.expects(:set_content_type).with(response, Puppet::Network::FormatHandler.format(:yaml)) - handler.do_destroy(indirection.name, "my data", {}, request, response) + handler.do_destroy(indirection, "my data", {}, request, response) end it "uses the first supported format for the response" do @@ -429,7 +427,7 @@ describe Puppet::Network::HTTP::Handler do handler.expects(:set_response).with(response, data.render(:pson)) handler.expects(:set_content_type).with(response, Puppet::Network::FormatHandler.format(:pson)) - handler.do_destroy(indirection.name, "my data", {}, request, response) + handler.do_destroy(indirection, "my data", {}, request, response) end it "raises an error and does not destory when no accepted formats are known" do @@ -438,7 +436,7 @@ describe Puppet::Network::HTTP::Handler do request = a_request_that_submits(data, :accept_header => "unknown, also/unknown") expect do - handler.do_destroy(indirection.name, "my data", {}, request, response) + handler.do_destroy(indirection, "my data", {}, request, response) end.to raise_error(Puppet::Network::HTTP::Handler::HTTPNotAcceptableError) Puppet::TestModel.indirection.find("my data").should_not be_nil @@ -460,7 +458,7 @@ describe Puppet::Network::HTTP::Handler do request[:content_type_header] = "application/x-raw" request[:body] = '' - handler.do_save(indirection.name, "test", {}, request, response) + handler.do_save(indirection, "test", {}, request, response) Puppet::TestModel.indirection.find("test").data.should == '' end @@ -469,7 +467,7 @@ describe Puppet::Network::HTTP::Handler do data = Puppet::TestModel.new("my data", "some data") request = a_request_that_submits(data) - handler.do_save(indirection.name, "my data", {}, request, response) + handler.do_save(indirection, "my data", {}, request, response) Puppet::TestModel.indirection.find("my data").should == data end @@ -481,7 +479,7 @@ describe Puppet::Network::HTTP::Handler do handler.expects(:set_response).with(response, data.render(:yaml)) handler.expects(:set_content_type).with(response, Puppet::Network::FormatHandler.format(:yaml)) - handler.do_save(indirection.name, "my data", {}, request, response) + handler.do_save(indirection, "my data", {}, request, response) end it "uses the first supported format for the response" do @@ -491,7 +489,7 @@ describe Puppet::Network::HTTP::Handler do handler.expects(:set_response).with(response, data.render(:pson)) handler.expects(:set_content_type).with(response, Puppet::Network::FormatHandler.format(:pson)) - handler.do_save(indirection.name, "my data", {}, request, response) + handler.do_save(indirection, "my data", {}, request, response) end it "raises an error and does not save when no accepted formats are known" do @@ -499,7 +497,7 @@ describe Puppet::Network::HTTP::Handler do request = a_request_that_submits(data, :accept_header => "unknown, also/unknown") expect do - handler.do_save(indirection.name, "my data", {}, request, response) + handler.do_save(indirection, "my data", {}, request, response) end.to raise_error(Puppet::Network::HTTP::Handler::HTTPNotAcceptableError) Puppet::TestModel.indirection.find("my data").should be_nil @@ -543,7 +541,8 @@ describe Puppet::Network::HTTP::Handler do end def set_response(response, body, status = 200) - "my_result" + response[:body] = body + response[:status] = status end def http_method(request) diff --git a/spec/unit/network/http_pool_spec.rb b/spec/unit/network/http_pool_spec.rb index c671e88f1..4ee507568 100755 --- a/spec/unit/network/http_pool_spec.rb +++ b/spec/unit/network/http_pool_spec.rb @@ -3,6 +3,10 @@ require 'spec_helper' require 'puppet/network/http_pool' describe Puppet::Network::HttpPool do + before :each do + Puppet::SSL::Key.indirection.terminus_class = :memory + Puppet::SSL::CertificateRequest.indirection.terminus_class = :memory + end describe "when managing http instances" do @@ -26,15 +30,14 @@ describe Puppet::Network::HttpPool do describe 'peer verification' do def setup_standard_ssl_configuration ca_cert_file = File.expand_path('/path/to/ssl/certs/ca_cert.pem') - FileTest.stubs(:exist?).with(ca_cert_file).returns(true) - ssl_configuration = stub('ssl_configuration', :ca_auth_file => ca_cert_file) - Puppet::Network::HTTP::Connection.any_instance.stubs(:ssl_configuration).returns(ssl_configuration) + Puppet[:ssl_client_ca_auth] = ca_cert_file + Puppet::FileSystem::File.stubs(:exist?).with(ca_cert_file).returns(true) end def setup_standard_hostcert host_cert_file = File.expand_path('/path/to/ssl/certs/host_cert.pem') - FileTest.stubs(:exist?).with(host_cert_file).returns(true) + Puppet::FileSystem::File.stubs(:exist?).with(host_cert_file).returns(true) Puppet[:hostcert] = host_cert_file end @@ -44,7 +47,7 @@ describe Puppet::Network::HttpPool do key = stub('key', :content => 'real_key') host = stub('host', :certificate => cert, :key => key, :ssl_store => stub('store')) - Puppet::Network::HTTP::Connection.any_instance.stubs(:ssl_host).returns(host) + Puppet::SSL::Host.stubs(:localhost).returns(host) end before do diff --git a/spec/unit/node/environment_spec.rb b/spec/unit/node/environment_spec.rb index 7b2d73b8a..be8ae1a94 100755 --- a/spec/unit/node/environment_spec.rb +++ b/spec/unit/node/environment_spec.rb @@ -20,111 +20,97 @@ describe Puppet::Node::Environment do it "should use the filetimeout for the ttl for the modulepath" do Puppet::Node::Environment.attr_ttl(:modulepath).should == Integer(Puppet[:filetimeout]) end - + it "should use the filetimeout for the ttl for the module list" do Puppet::Node::Environment.attr_ttl(:modules).should == Integer(Puppet[:filetimeout]) end - + it "should use the default environment if no name is provided while initializing an environment" do Puppet[:environment] = "one" Puppet::Node::Environment.new.name.should == :one end - + it "should treat environment instances as singletons" do Puppet::Node::Environment.new("one").should equal(Puppet::Node::Environment.new("one")) end - + it "should treat an environment specified as names or strings as equivalent" do Puppet::Node::Environment.new(:one).should equal(Puppet::Node::Environment.new("one")) end - + it "should return its name when converted to a string" do Puppet::Node::Environment.new(:one).to_s.should == "one" end - + it "should just return any provided environment if an environment is provided as the name" do one = Puppet::Node::Environment.new(:one) Puppet::Node::Environment.new(one).should equal(one) end - + describe "when managing known resource types" do before do @collection = Puppet::Resource::TypeCollection.new(env) env.stubs(:perform_initial_import).returns(Puppet::Parser::AST::Hostclass.new('')) - Thread.current[:known_resource_types] = nil + $known_resource_types = nil end - + it "should create a resource type collection if none exists" do Puppet::Resource::TypeCollection.expects(:new).with(env).returns @collection env.known_resource_types.should equal(@collection) end - + it "should reuse any existing resource type collection" do env.known_resource_types.should equal(env.known_resource_types) end - + it "should perform 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 "should return the same collection even if stale if it's the same thread" do Puppet::Resource::TypeCollection.stubs(:new).returns @collection env.known_resource_types.stubs(:stale?).returns true - + env.known_resource_types.should equal(@collection) end - + it "should return the current thread associated collection if there is one" do - Thread.current[:known_resource_types] = @collection - + $known_resource_types = @collection + env.known_resource_types.should equal(@collection) end - - it "should give to all threads using the same environment the same collection if the collection isn't stale" do - @original_thread_type_collection = Puppet::Resource::TypeCollection.new(env) - Puppet::Resource::TypeCollection.expects(:new).with(env).returns @original_thread_type_collection - env.known_resource_types.should equal(@original_thread_type_collection) - - @original_thread_type_collection.expects(:require_reparse?).returns(false) - Puppet::Resource::TypeCollection.stubs(:new).with(env).returns @collection - - t = Thread.new { - env.known_resource_types.should equal(@original_thread_type_collection) - } - t.join - end - + it "should generate a new TypeCollection if the current one requires reparsing" do old_type_collection = env.known_resource_types old_type_collection.stubs(:require_reparse?).returns true - Thread.current[:known_resource_types] = nil + $known_resource_types = nil new_type_collection = env.known_resource_types - + new_type_collection.should be_a Puppet::Resource::TypeCollection new_type_collection.should_not equal(old_type_collection) end end - + it "should validate the modulepath directories" do real_file = tmpdir('moduledir') path = %W[/one /two #{real_file}].join(File::PATH_SEPARATOR) - + Puppet[:modulepath] = path - + env.modulepath.should == [real_file] end - + it "should prefix the value of the 'PUPPETLIB' environment variable to the module path if present" do Puppet::Util.withenv("PUPPETLIB" => %w{/l1 /l2}.join(File::PATH_SEPARATOR)) do module_path = %w{/one /two}.join(File::PATH_SEPARATOR) env.expects(:validate_dirs).with(%w{/l1 /l2 /one /two}).returns %w{/l1 /l2 /one /two} env.expects(:[]).with(:modulepath).returns module_path - + env.modulepath.should == %w{/l1 /l2 /one /two} end end - + describe "when validating modulepath or manifestdir directories" do before :each do @path_one = tmpdir("path_one") @@ -132,69 +118,69 @@ describe Puppet::Node::Environment do sep = File::PATH_SEPARATOR Puppet[:modulepath] = "#{@path_one}#{sep}#{@path_two}" end - + it "should not return non-directories" do FileTest.expects(:directory?).with(@path_one).returns true FileTest.expects(:directory?).with(@path_two).returns false - + env.validate_dirs([@path_one, @path_two]).should == [@path_one] end - + it "should use the current working directory to fully-qualify unqualified paths" do FileTest.stubs(:directory?).returns true - two = File.expand_path("two") + env.validate_dirs([@path_one, 'two']).should == [@path_one, two] end end - + describe "when modeling a specific environment" do it "should have a method for returning the environment name" do Puppet::Node::Environment.new("testing").name.should == :testing end - + it "should provide an array-like accessor method for returning any environment-specific setting" do env.should respond_to(:[]) end - + it "should ask the Puppet settings instance for the setting qualified with the environment name" do Puppet.settings.set_value(:server, "myval", :testing) env[:server].should == "myval" end - + it "should be able to return an individual module that exists in its module path" do env.stubs(:modules).returns [Puppet::Module.new('one', "/one", mock("env"))] - + mod = env.module('one') mod.should be_a(Puppet::Module) mod.name.should == 'one' end - + it "should not return a module if the module doesn't exist" do env.stubs(:modules).returns [Puppet::Module.new('one', "/one", mock("env"))] - + env.module('two').should be_nil end - + it "should return nil if asked for a module that does not exist in its path" do modpath = tmpdir('modpath') env.modulepath = [modpath] - + env.module("one").should be_nil end - + describe "module data" do before do dir = tmpdir("deep_path") - + @first = File.join(dir, "first") @second = File.join(dir, "second") Puppet[:modulepath] = "#{@first}#{File::PATH_SEPARATOR}#{@second}" - + FileUtils.mkdir_p(@first) FileUtils.mkdir_p(@second) end - + describe "#modules_by_path" do it "should return an empty list if there are no modules" do env.modules_by_path.should == { @@ -202,19 +188,19 @@ describe Puppet::Node::Environment do @second => [] } end - + it "should include modules even if they exist in multiple dirs in the modulepath" do modpath1 = File.join(@first, "foo") FileUtils.mkdir_p(modpath1) modpath2 = File.join(@second, "foo") FileUtils.mkdir_p(modpath2) - + env.modules_by_path.should == { @first => [Puppet::Module.new('foo', modpath1, env)], @second => [Puppet::Module.new('foo', modpath2, env)] } end - + it "should ignore modules with invalid names" do FileUtils.mkdir_p(File.join(@first, 'foo')) FileUtils.mkdir_p(File.join(@first, 'foo2')) @@ -226,12 +212,12 @@ describe Puppet::Node::Environment do FileUtils.mkdir_p(File.join(@first, '-foo')) FileUtils.mkdir_p(File.join(@first, 'foo-')) FileUtils.mkdir_p(File.join(@first, 'foo--bar')) - + env.modules_by_path[@first].collect{|mod| mod.name}.sort.should == %w{foo foo-bar foo2 foo_bar} end - + end - + describe "#module_requirements" do it "should return a list of what modules depend on other modules" do PuppetSpec::Modules.create( @@ -266,7 +252,7 @@ describe Puppet::Node::Environment do :dependencies => [{ 'name' => 'puppetlabs/bar', "version_requirement" => "~3.0.0" }] } ) - + env.module_requirements.should == { 'puppetlabs/alpha' => [], 'puppetlabs/foo' => [ @@ -297,7 +283,7 @@ describe Puppet::Node::Environment do } end end - + describe ".module_by_forge_name" do it "should find modules by forge_name" do mod = PuppetSpec::Modules.create( @@ -308,7 +294,7 @@ describe Puppet::Node::Environment do ) env.module_by_forge_name('puppetlabs/baz').should == mod end - + it "should not find modules with same name by the wrong author" do mod = PuppetSpec::Modules.create( 'baz', @@ -318,17 +304,17 @@ describe Puppet::Node::Environment do ) env.module_by_forge_name('puppetlabs/baz').should == nil end - + it "should return nil when the module can't be found" do env.module_by_forge_name('ima/nothere').should be_nil end end - + describe ".modules" do it "should return an empty list if there are no modules" do env.modules.should == [] end - + it "should return a module named for every directory in each module path" do %w{foo bar}.each do |mod_name| FileUtils.mkdir_p(File.join(@first, mod_name)) @@ -338,14 +324,14 @@ describe Puppet::Node::Environment do end env.modules.collect{|mod| mod.name}.sort.should == %w{foo bar bee baz}.sort end - + it "should remove duplicates" do FileUtils.mkdir_p(File.join(@first, 'foo')) FileUtils.mkdir_p(File.join(@second, 'foo')) - + env.modules.collect{|mod| mod.name}.sort.should == %w{foo} end - + it "should ignore modules with invalid names" do FileUtils.mkdir_p(File.join(@first, 'foo')) FileUtils.mkdir_p(File.join(@first, 'foo2')) @@ -353,63 +339,63 @@ describe Puppet::Node::Environment do FileUtils.mkdir_p(File.join(@first, 'foo_bar')) FileUtils.mkdir_p(File.join(@first, 'foo=bar')) FileUtils.mkdir_p(File.join(@first, 'foo bar')) - + env.modules.collect{|mod| mod.name}.sort.should == %w{foo foo-bar foo2 foo_bar} end - + it "should create modules with the correct environment" do FileUtils.mkdir_p(File.join(@first, 'foo')) env.modules.each {|mod| mod.environment.should == env } end - + end end - + it "should cache the module list" do env.modulepath = %w{/a} Dir.expects(:entries).once.with("/a").returns %w{foo} - + env.modules env.modules end end - + describe Puppet::Node::Environment::Helper do before do @helper = Object.new @helper.extend(Puppet::Node::Environment::Helper) end - + it "should be able to set and retrieve the environment as a symbol" do @helper.environment = :foo @helper.environment.name.should == :foo end - + it "should accept an environment directly" do @helper.environment = Puppet::Node::Environment.new(:foo) @helper.environment.name.should == :foo end - + it "should accept an environment as a string" do @helper.environment = 'foo' @helper.environment.name.should == :foo end end - + describe "when performing initial import" do before do @parser = Puppet::Parser::ParserFactory.parser("test") # @parser = Puppet::Parser::EParserAdapter.new(Puppet::Parser::Parser.new("test")) # TODO: FIX PARSER FACTORY Puppet::Parser::ParserFactory.stubs(:parser).returns @parser end - + it "should set the parser's string to the 'code' setting and parse if code is available" do Puppet.settings[:code] = "my code" @parser.expects(:string=).with "my code" @parser.expects(:parse) env.instance_eval { perform_initial_import } end - + it "should set the parser's file to the 'manifest' setting and parse if no code is available and the manifest is available" do filename = tmpfile('myfile') File.open(filename, 'w'){|f| } @@ -418,7 +404,7 @@ describe Puppet::Node::Environment do @parser.expects(:parse) env.instance_eval { perform_initial_import } end - + it "should pass the manifest file to the parser even if it does not exist on disk" do filename = tmpfile('myfile') Puppet.settings[:code] = "" @@ -427,15 +413,15 @@ describe Puppet::Node::Environment do @parser.expects(:parse).once env.instance_eval { perform_initial_import } end - + it "should fail helpfully if there is an error importing" do - File.stubs(:exist?).returns true + Puppet::FileSystem::File.stubs(:exist?).returns true env.stubs(:known_resource_types).returns Puppet::Resource::TypeCollection.new(env) @parser.expects(:file=).once @parser.expects(:parse).raises ArgumentError lambda { env.instance_eval { perform_initial_import } }.should raise_error(Puppet::Error) end - + it "should not do anything if the ignore_import settings is set" do Puppet.settings[:ignoreimport] = true @parser.expects(:string=).never @@ -443,11 +429,11 @@ describe Puppet::Node::Environment do @parser.expects(:parse).never env.instance_eval { perform_initial_import } end - + it "should mark the type collection as needing a reparse when there is an error parsing" do @parser.expects(:parse).raises Puppet::ParseError.new("Syntax error at ...") env.stubs(:known_resource_types).returns Puppet::Resource::TypeCollection.new(env) - + lambda { env.instance_eval { perform_initial_import } }.should raise_error(Puppet::Error, /Syntax error at .../) env.known_resource_types.require_reparse?.should be_true end @@ -465,5 +451,5 @@ describe Puppet::Node::Environment do end it_behaves_like 'the environment' end - + end diff --git a/spec/unit/node/facts_spec.rb b/spec/unit/node/facts_spec.rb index 9ba6baa11..55e3dbbf6 100755 --- a/spec/unit/node/facts_spec.rb +++ b/spec/unit/node/facts_spec.rb @@ -1,8 +1,17 @@ #! /usr/bin/env ruby require 'spec_helper' -require 'matchers/json' require 'puppet/node/facts' +# the json-schema gem doesn't support windows +if not Puppet.features.microsoft_windows? + describe "catalog facts schema" do + it "should validate against the json meta-schema" do + JSON::Validator.validate!(JSON_META_SCHEMA, FACTS_SCHEMA) + end + end + +end + describe Puppet::Node::Facts, "when indirecting" do before do @facts = Puppet::Node::Facts.new("me") @@ -143,8 +152,16 @@ describe Puppet::Node::Facts, "when indirecting" do result = PSON.parse(facts.to_pson) result['name'].should == facts.name result['values'].should == facts.values.reject { |key, value| key.to_s =~ /_/ } - result['timestamp'].should == facts.timestamp.to_s - result['expiration'].should == facts.expiration.to_s + result['timestamp'].should == facts.timestamp.iso8601(9) + result['expiration'].should == facts.expiration.iso8601(9) + end + + it "should generate valid facts data against the facts schema", :unless => Puppet.features.microsoft_windows? do + Time.stubs(:now).returns(@timestamp) + facts = Puppet::Node::Facts.new("foo", {'a' => 1, 'b' => 2, 'c' => 3}) + facts.expiration = @expiration + + JSON::Validator.validate!(FACTS_SCHEMA, facts.to_pson) end it "should not include nil values" do diff --git a/spec/unit/node_spec.rb b/spec/unit/node_spec.rb index 5c36149ad..f8691ed9d 100755 --- a/spec/unit/node_spec.rb +++ b/spec/unit/node_spec.rb @@ -2,6 +2,17 @@ require 'spec_helper' require 'matchers/json' +# the json-schema gem doesn't support windows +if not Puppet.features.microsoft_windows? + NODE_SCHEMA = JSON.parse(File.read(File.join(File.dirname(__FILE__), '../../api/schemas/node.json'))) + + describe "node schema" do + it "should validate against the json meta-schema" do + JSON::Validator.validate!(JSON_META_SCHEMA, NODE_SCHEMA) + end + end +end + describe Puppet::Node do it "should register its document type as Node" do PSON.registered_document_types["Node"].should equal(Puppet::Node) @@ -55,6 +66,38 @@ describe Puppet::Node do new_node.name.should == node.name end + it "can round-trip through pson" do + facts = Puppet::Node::Facts.new("hello", "one" => "c", "two" => "b") + node = Puppet::Node.new("hello", + :environment => 'kjhgrg', + :classes => ['erth', 'aiu'], + :parameters => {"hostname"=>"food"} + ) + new_node = Puppet::Node.convert_from('pson', node.render('pson')) + new_node.environment.should == node.environment + new_node.parameters.should == node.parameters + new_node.classes.should == node.classes + new_node.name.should == node.name + end + + it "validates against the node json schema", :unless => Puppet.features.microsoft_windows? do + facts = Puppet::Node::Facts.new("hello", "one" => "c", "two" => "b") + node = Puppet::Node.new("hello", + :environment => 'kjhgrg', + :classes => ['erth', 'aiu'], + :parameters => {"hostname"=>"food"} + ) + JSON::Validator.validate!(NODE_SCHEMA, node.to_pson) + end + + it "when missing optional parameters validates against the node json schema", :unless => Puppet.features.microsoft_windows? do + facts = Puppet::Node::Facts.new("hello", "one" => "c", "two" => "b") + node = Puppet::Node.new("hello", + :environment => 'kjhgrg' + ) + JSON::Validator.validate!(NODE_SCHEMA, node.to_pson) + end + describe "when converting to json" do before do @node = Puppet::Node.new("mynode") diff --git a/spec/unit/parameter/boolean_spec.rb b/spec/unit/parameter/boolean_spec.rb index 7039c42fc..505bc561f 100644 --- a/spec/unit/parameter/boolean_spec.rb +++ b/spec/unit/parameter/boolean_spec.rb @@ -5,21 +5,31 @@ require 'puppet/parameter/boolean' describe Puppet::Parameter::Boolean do let (:resource) { mock('resource') } - subject { described_class.new(:resource => resource) } - - [ true, :true, 'true', :yes, 'yes', 'TrUe', 'yEs' ].each do |arg| - it "should munge #{arg.inspect} as true" do - subject.munge(arg).should == true + describe "after initvars" do + before { described_class.initvars } + it "should have the correct value_collection" do + described_class.value_collection.values.sort.should == + [:true, :false, :yes, :no].sort end end - [ false, :false, 'false', :no, 'no', 'FaLSE', 'nO' ].each do |arg| - it "should munge #{arg.inspect} as false" do - subject.munge(arg).should == false + + describe "instances" do + subject { described_class.new(:resource => resource) } + + [ true, :true, 'true', :yes, 'yes', 'TrUe', 'yEs' ].each do |arg| + it "should munge #{arg.inspect} as true" do + subject.munge(arg).should == true + end end - end - [ nil, :undef, 'undef', '0', 0, '1', 1, 9284 ].each do |arg| - it "should fail to munge #{arg.inspect}" do - expect { subject.munge(arg) }.to raise_error Puppet::Error + [ false, :false, 'false', :no, 'no', 'FaLSE', 'nO' ].each do |arg| + it "should munge #{arg.inspect} as false" do + subject.munge(arg).should == false + end + end + [ nil, :undef, 'undef', '0', 0, '1', 1, 9284 ].each do |arg| + it "should fail to munge #{arg.inspect}" do + expect { subject.munge(arg) }.to raise_error Puppet::Error + end end end end diff --git a/spec/unit/parser/ast/resourceparam_spec.rb b/spec/unit/parser/ast/resourceparam_spec.rb new file mode 100644 index 000000000..818f146d3 --- /dev/null +++ b/spec/unit/parser/ast/resourceparam_spec.rb @@ -0,0 +1,51 @@ +#! /usr/bin/env ruby +require 'spec_helper' + +describe Puppet::Parser::AST::ResourceParam do + + ast = Puppet::Parser::AST + + before :each do + @compiler = Puppet::Parser::Compiler.new(Puppet::Node.new("mynode")) + @scope = Puppet::Parser::Scope.new(@compiler) + @params = ast::ASTArray.new({}) + @compiler.stubs(:add_override) + end + + it "should evaluate the parameter value" do + object = mock 'object' + object.expects(:safeevaluate).with(@scope).returns('value') + ast::ResourceParam.new(:param => 'myparam', :value => object).evaluate(@scope) + end + + it "should return a Puppet::Parser::Resource::Param on evaluation" do + object = mock 'object' + object.expects(:safeevaluate).with(@scope).returns('value') + evaled = ast::ResourceParam.new(:param => 'myparam', :value => object).evaluate(@scope) + evaled.should be_a(Puppet::Parser::Resource::Param) + evaled.name.to_s.should == 'myparam' + evaled.value.to_s.should == 'value' + end + + it "should copy line numbers to Puppet::Parser::Resource::Param" do + object = mock 'object' + object.expects(:safeevaluate).with(@scope).returns('value') + evaled = ast::ResourceParam.new(:param => 'myparam', :value => object, :line => 42).evaluate(@scope) + evaled.line.should == 42 + end + + it "should copy source file to Puppet::Parser::Resource::Param" do + object = mock 'object' + object.expects(:safeevaluate).with(@scope).returns('value') + evaled = ast::ResourceParam.new(:param => 'myparam', :value => object, :file => 'foo.pp').evaluate(@scope) + evaled.file.should == 'foo.pp' + end + + it "should change nil parameter values to undef" do + object = mock 'object' + object.expects(:safeevaluate).with(@scope).returns(nil) + evaled = ast::ResourceParam.new(:param => 'myparam', :value => object).evaluate(@scope) + evaled.should be_a(Puppet::Parser::Resource::Param) + evaled.value.should == :undef + end +end diff --git a/spec/unit/parser/compiler_spec.rb b/spec/unit/parser/compiler_spec.rb index 8e71756e1..68210a4df 100755 --- a/spec/unit/parser/compiler_spec.rb +++ b/spec/unit/parser/compiler_spec.rb @@ -1,5 +1,6 @@ #! /usr/bin/env ruby require 'spec_helper' +require 'puppet_spec/compiler' class CompilerTestResource attr_accessor :builtin, :virtual, :evaluated, :type, :title @@ -106,22 +107,20 @@ describe Puppet::Parser::Compiler do @compiler.classlist.sort.should == %w{one two}.sort end - it "should clear the thread local caches before compile" do + it "should clear the global caches before compile" do compiler = stub 'compiler' Puppet::Parser::Compiler.expects(:new).with(@node).returns compiler catalog = stub 'catalog' compiler.expects(:compile).returns catalog catalog.expects(:to_resource) - [:known_resource_types, :env_module_directories].each do |var| - Thread.current[var] = "rspec" - end + $known_resource_types = "rspec" + $env_module_directories = "rspec" Puppet::Parser::Compiler.compile(@node) - [:known_resource_types, :env_module_directories].each do |var| - Thread.current[var].should == nil - end + $known_resource_types = nil + $env_module_directories = nil end describe "when initializing" do @@ -218,27 +217,6 @@ describe Puppet::Parser::Compiler do @compiler.catalog.server_version.should == "3" end - it "should evaluate any existing classes named in the node" do - classes = %w{one two three four} - main = stub 'main' - one = stub 'one', :name => "one" - three = stub 'three', :name => "three" - @node.stubs(:name).returns("whatever") - @node.stubs(:classes).returns(classes) - compile_stub(:evaluate_node_classes) - - @compiler.expects(:evaluate_classes).with(classes, @compiler.topscope) - @compiler.compile - end - - it "should evaluate any parameterized classes named in the node" do - classes = {'foo'=>{'p1'=>'one'}, 'bar'=>{'p2'=>'two'}} - @node.stubs(:classes).returns(classes) - @compiler.expects(:evaluate_classes).with(classes, @compiler.topscope) - @compiler.compile - 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, "") @@ -262,12 +240,6 @@ describe Puppet::Parser::Compiler do @compiler.catalog.edge?(stage, klass).should be_true end - it "should evaluate any node classes" do - @node.stubs(:classes).returns(%w{one two three four}) - @compiler.expects(:evaluate_classes).with(%w{one two three four}, @compiler.topscope) - @compiler.send(:evaluate_node_classes) - end - it "should evaluate all added collections" do colls = [] # And when the collections fail to evaluate. @@ -655,7 +627,7 @@ describe Puppet::Parser::Compiler do catalog = @compiler.compile r2 = catalog.resources.detect {|r| r.title == 'Bar::Foo' } - r2.tags.should =~ ['bar::foo', 'class', 'bar', 'foo'] + r2.tags.should == Puppet::Util::TagSet.new(['bar::foo', 'class', 'bar', 'foo']) end end @@ -791,6 +763,102 @@ describe Puppet::Parser::Compiler do 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 + end + end + end + describe "when managing resource overrides" do before do diff --git a/spec/unit/parser/eparser_adapter_spec.rb b/spec/unit/parser/eparser_adapter_spec.rb index 051633434..173cfb783 100644 --- a/spec/unit/parser/eparser_adapter_spec.rb +++ b/spec/unit/parser/eparser_adapter_spec.rb @@ -365,42 +365,42 @@ describe Puppet::Parser do end context "when parsing method calls" do it "should parse method call with one param lambda" do - expect { @parser.parse("$a.foreach {|$a| debug $a }") }.to_not raise_error + expect { @parser.parse("$a.each |$a|{ debug $a }") }.to_not raise_error end it "should parse method call with two param lambda" do - expect { @parser.parse("$a.foreach {|$a,$b| debug $a }") }.to_not raise_error + expect { @parser.parse("$a.each |$a,$b|{ debug $a }") }.to_not raise_error end it "should parse method call with two param lambda and default value" do - expect { @parser.parse("$a.foreach {|$a,$b=1| debug $a }") }.to_not raise_error + expect { @parser.parse("$a.each |$a,$b=1|{ debug $a }") }.to_not raise_error end it "should parse method call without lambda (statement)" do - expect { @parser.parse("$a.foreach") }.to_not raise_error + expect { @parser.parse("$a.each") }.to_not raise_error end it "should parse method call without lambda (expression)" do - expect { @parser.parse("$x = $a.foreach + 1") }.to_not raise_error + expect { @parser.parse("$x = $a.each + 1") }.to_not raise_error end context "a receiver expression of type" do it "variable should be allowed" do - expect { @parser.parse("$a.foreach") }.to_not raise_error + expect { @parser.parse("$a.each") }.to_not raise_error end it "hasharrayaccess should be allowed" do - expect { @parser.parse("$a[0][1].foreach") }.to_not raise_error + expect { @parser.parse("$a[0][1].each") }.to_not raise_error end it "quoted text should be allowed" do - expect { @parser.parse("\"monkey\".foreach") }.to_not raise_error - expect { @parser.parse("'monkey'.foreach") }.to_not raise_error + expect { @parser.parse("\"monkey\".each") }.to_not raise_error + expect { @parser.parse("'monkey'.each") }.to_not raise_error end it "selector text should be allowed" do - expect { @parser.parse("$a ? { 'banana'=>[1,2,3]}.foreach") }.to_not raise_error + expect { @parser.parse("$a ? { 'banana'=>[1,2,3]}.each") }.to_not raise_error end it "function call should be allowed" do - expect { @parser.parse("duh(1,2,3).foreach") }.to_not raise_error + expect { @parser.parse("duh(1,2,3).each") }.to_not raise_error end it "method call should be allowed" do expect { @parser.parse("$a.foo.bar") }.to_not raise_error end it "chained method calls with lambda should be allowed" do - expect { @parser.parse("$a.foo{||}.bar{||}") }.to_not raise_error + expect { @parser.parse("$a.foo||{}.bar||{}") }.to_not raise_error end end end diff --git a/spec/unit/parser/files_spec.rb b/spec/unit/parser/files_spec.rb index 2e84216cb..ca7e45b13 100755 --- a/spec/unit/parser/files_spec.rb +++ b/spec/unit/parser/files_spec.rb @@ -28,7 +28,7 @@ describe Puppet::Parser::Files do Puppet[:templatedir] = "/my/templates" Puppet[:modulepath] = "/one:/two" File.stubs(:directory?).returns(true) - FileTest.stubs(:exist?).returns(true) + Puppet::FileSystem::File.stubs(:exist?).returns(true) Puppet::Parser::Files.find_template("mymod/mytemplate").should == File.join(Puppet[:templatedir], "mymod/mytemplate") end @@ -43,59 +43,59 @@ describe Puppet::Parser::Files do end it "should return unqualified templates if they exist in the template dir" do - FileTest.stubs(:exist?).returns true + Puppet::FileSystem::File.stubs(:exist?).returns true Puppet::Parser::Files.stubs(:templatepath).with(nil).returns(["/my/templates"]) Puppet::Parser::Files.find_template("mytemplate").should == "/my/templates/mytemplate" end it "should only return templates if they actually exist" do - FileTest.expects(:exist?).with("/my/templates/mytemplate").returns true + Puppet::FileSystem::File.expects(:exist?).with("/my/templates/mytemplate").returns true Puppet::Parser::Files.stubs(:templatepath).with(nil).returns(["/my/templates"]) Puppet::Parser::Files.find_template("mytemplate").should == "/my/templates/mytemplate" end it "should return nil when asked for a template that doesn't exist" do - FileTest.expects(:exist?).with("/my/templates/mytemplate").returns false + Puppet::FileSystem::File.expects(:exist?).with("/my/templates/mytemplate").returns false Puppet::Parser::Files.stubs(:templatepath).with(nil).returns(["/my/templates"]) Puppet::Parser::Files.find_template("mytemplate").should be_nil end it "should search in the template directories before modules" do - FileTest.stubs(:exist?).returns true + Puppet::FileSystem::File.stubs(:exist?).returns true Puppet::Parser::Files.stubs(:templatepath).with(nil).returns(["/my/templates"]) Puppet::Module.expects(:find).never Puppet::Parser::Files.find_template("mytemplate") end it "should accept relative templatedirs" do - FileTest.stubs(:exist?).returns true + Puppet::FileSystem::File.stubs(:exist?).returns true Puppet[:templatedir] = "my/templates" File.expects(:directory?).with(File.expand_path("my/templates")).returns(true) Puppet::Parser::Files.find_template("mytemplate").should == File.expand_path("my/templates/mytemplate") end it "should use the environment templatedir if no module is found and an environment is specified" do - FileTest.stubs(:exist?).returns true + Puppet::FileSystem::File.stubs(:exist?).returns true Puppet::Parser::Files.stubs(:templatepath).with("myenv").returns(["/myenv/templates"]) Puppet::Parser::Files.find_template("mymod/mytemplate", "myenv").should == "/myenv/templates/mymod/mytemplate" end it "should use first dir from environment templatedir if no module is found and an environment is specified" do - FileTest.stubs(:exist?).returns true + Puppet::FileSystem::File.stubs(:exist?).returns true Puppet::Parser::Files.stubs(:templatepath).with("myenv").returns(["/myenv/templates", "/two/templates"]) Puppet::Parser::Files.find_template("mymod/mytemplate", "myenv").should == "/myenv/templates/mymod/mytemplate" end it "should use a valid dir when templatedir is a path for unqualified templates and the first dir contains template" do Puppet::Parser::Files.stubs(:templatepath).returns(["/one/templates", "/two/templates"]) - FileTest.expects(:exist?).with("/one/templates/mytemplate").returns(true) + Puppet::FileSystem::File.expects(:exist?).with("/one/templates/mytemplate").returns(true) Puppet::Parser::Files.find_template("mytemplate").should == "/one/templates/mytemplate" end it "should use a valid dir when templatedir is a path for unqualified templates and only second dir contains template" do Puppet::Parser::Files.stubs(:templatepath).returns(["/one/templates", "/two/templates"]) - FileTest.expects(:exist?).with("/one/templates/mytemplate").returns(false) - FileTest.expects(:exist?).with("/two/templates/mytemplate").returns(true) + Puppet::FileSystem::File.expects(:exist?).with("/one/templates/mytemplate").returns(false) + Puppet::FileSystem::File.expects(:exist?).with("/two/templates/mytemplate").returns(true) Puppet::Parser::Files.find_template("mytemplate").should == "/two/templates/mytemplate" end diff --git a/spec/unit/parser/functions/contain_spec.rb b/spec/unit/parser/functions/contain_spec.rb new file mode 100644 index 000000000..3150e0c8e --- /dev/null +++ b/spec/unit/parser/functions/contain_spec.rb @@ -0,0 +1,185 @@ +#! /usr/bin/env ruby +require 'spec_helper' +require 'puppet_spec/compiler' +require 'puppet/parser/functions' +require 'matchers/containment_matchers' +require 'matchers/include_in_order' + +describe 'The "contain" function' do + include PuppetSpec::Compiler + include ContainmentMatchers + + it "includes the class" do + catalog = compile_to_catalog(<<-MANIFEST) + class contained { + notify { "contained": } + } + + class container { + contain contained + } + + include container + MANIFEST + + expect(catalog.classes).to include("contained") + end + + it "makes the class contained in the current class" do + catalog = compile_to_catalog(<<-MANIFEST) + class contained { + notify { "contained": } + } + + class container { + contain contained + } + + include container + MANIFEST + + expect(catalog).to contain_class("contained").in("container") + end + + it "can contain multiple classes" do + catalog = compile_to_catalog(<<-MANIFEST) + class a { + notify { "a": } + } + + class b { + notify { "b": } + } + + class container { + contain a, b + } + + include container + MANIFEST + + expect(catalog).to contain_class("a").in("container") + expect(catalog).to contain_class("b").in("container") + end + + context "when containing a class in multiple classes" do + it "creates a catalog with all containment edges" do + catalog = compile_to_catalog(<<-MANIFEST) + class contained { + notify { "contained": } + } + + class container { + contain contained + } + + class another { + contain contained + } + + include container + include another + MANIFEST + + expect(catalog).to contain_class("contained").in("container") + expect(catalog).to contain_class("contained").in("another") + end + + it "and there are no dependencies applies successfully" do + manifest = <<-MANIFEST + class contained { + notify { "contained": } + } + + class container { + contain contained + } + + class another { + contain contained + } + + include container + include another + MANIFEST + + expect { apply_compiled_manifest(manifest) }.not_to raise_error + end + + it "and there are explicit dependencies on the containing class causes a dependency cycle" do + manifest = <<-MANIFEST + class contained { + notify { "contained": } + } + + class container { + contain contained + } + + class another { + contain contained + } + + include container + include another + + Class["container"] -> Class["another"] + MANIFEST + + expect { apply_compiled_manifest(manifest) }.to raise_error( + Puppet::Error, + /Found 1 dependency cycle/ + ) + end + end + + it "does not create duplicate edges" do + catalog = compile_to_catalog(<<-MANIFEST) + class contained { + notify { "contained": } + } + + class container { + contain contained + contain contained + } + + include container + MANIFEST + + contained = catalog.resource("Class", "contained") + container = catalog.resource("Class", "container") + + expect(catalog.edges_between(container, contained)).to have(1).item + end + + context "when a containing class has a dependency order" do + it "the contained class is applied in that order" do + catalog = compile_to_relationship_graph(<<-MANIFEST) + class contained { + notify { "contained": } + } + + class container { + contain contained + } + + class first { + notify { "first": } + } + + class last { + notify { "last": } + } + + include container, first, last + + Class["first"] -> Class["container"] -> Class["last"] + MANIFEST + + expect(order_resources_traversed_in(catalog)).to include_in_order( + "Notify[first]", "Notify[contained]", "Notify[last]" + ) + end + end +end diff --git a/spec/unit/parser/functions/create_resources_spec.rb b/spec/unit/parser/functions/create_resources_spec.rb index a0c3253ab..79ed02f22 100755 --- a/spec/unit/parser/functions/create_resources_spec.rb +++ b/spec/unit/parser/functions/create_resources_spec.rb @@ -23,6 +23,14 @@ describe 'function for dynamically creating resources' do expect { @scope.function_create_resources(['foo', 'bar', 'blah', 'baz']) }.to raise_error(ArgumentError, 'create_resources(): wrong number of arguments (4; must be 2 or 3)') end + it 'should require second argument to be a hash' do + expect { @scope.function_create_resources(['foo','bar']) }.to raise_error(ArgumentError, 'create_resources(): second argument must be a hash') + end + + it 'should require optional third argument to be a hash' do + expect { @scope.function_create_resources(['foo',{},'foo']) }.to raise_error(ArgumentError, 'create_resources(): third argument, if provided, must be a hash') + end + describe 'when creating native types' do it 'empty hash should not cause resources to be added' do noop_catalog = compile_to_catalog("create_resources('file', {})") @@ -75,12 +83,12 @@ describe 'function for dynamically creating resources' do end describe 'when dynamically creating resource types' do - it 'should be able to create defined resoure types' do + it 'should be able to create defined resource types' do catalog = compile_to_catalog(<<-MANIFEST) define foocreateresource($one) { notify { $name: message => $one } } - + create_resources('foocreateresource', {'blah'=>{'one'=>'two'}}) MANIFEST catalog.resource(:notify, "blah")['message'].should == 'two' @@ -92,7 +100,7 @@ describe 'function for dynamically creating resources' do define foocreateresource($one) { notify { $name: message => $one } } - + create_resources('foocreateresource', {'blah'=>{}}) MANIFEST }.to raise_error(Puppet::Error, 'Must pass one to Foocreateresource[blah] on node foonode') @@ -103,7 +111,7 @@ describe 'function for dynamically creating resources' do define foocreateresource($one) { notify { $name: message => $one } } - + create_resources('foocreateresource', {'blah'=>{'one'=>'two'}, 'blaz'=>{'one'=>'three'}}) MANIFEST @@ -118,7 +126,7 @@ describe 'function for dynamically creating resources' do } notify { test: } - + create_resources('foocreateresource', {'blah'=>{'one'=>'two', 'require' => 'Notify[test]'}}) MANIFEST diff --git a/spec/unit/parser/functions/generate_spec.rb b/spec/unit/parser/functions/generate_spec.rb index 90afbc8ea..593703d63 100755 --- a/spec/unit/parser/functions/generate_spec.rb +++ b/spec/unit/parser/functions/generate_spec.rb @@ -45,7 +45,7 @@ describe "the generate function" do scope.function_generate([command]).should == 'yay' end - describe "on Windows", :as_platform => :windows do + describe "on Windows", :if => Puppet.features.microsoft_windows? do it "should accept the tilde in the path" do command = "C:/DOCUME~1/ADMINI~1/foo.bat" Dir.expects(:chdir).with(File.dirname(command)).returns("yay") diff --git a/spec/unit/parser/functions_spec.rb b/spec/unit/parser/functions_spec.rb index 81ff655a3..8ad33c874 100755 --- a/spec/unit/parser/functions_spec.rb +++ b/spec/unit/parser/functions_spec.rb @@ -128,7 +128,7 @@ describe Puppet::Parser::Functions do describe "::get_function" do it "can retrieve a function defined on the *root* environment" do - Thread.current[:environment] = nil + $environment = nil function = Puppet::Parser::Functions.newfunction("atest", :type => :rvalue) do nil end @@ -162,7 +162,7 @@ describe Puppet::Parser::Functions do describe "::merged_functions" do it "returns functions in both the current and root environment" do - Thread.current[:environment] = nil + $environment = nil func_a = Puppet::Parser::Functions.newfunction("test_a", :type => :rvalue) do nil end diff --git a/spec/unit/parser/lexer_spec.rb b/spec/unit/parser/lexer_spec.rb index fc8394cb1..972a8f1bf 100755 --- a/spec/unit/parser/lexer_spec.rb +++ b/spec/unit/parser/lexer_spec.rb @@ -861,7 +861,7 @@ describe "when trying to lex a non-existent file" do it "should return an empty list of tokens" do lexer = Puppet::Parser::Lexer.new lexer.file = nofile = tmpfile('lexer') - File.exists?(nofile).should == false + Puppet::FileSystem::File.exist?(nofile).should == false lexer.fullscan.should == [[false,false]] end diff --git a/spec/unit/parser/methods/collect_spec.rb b/spec/unit/parser/methods/collect_spec.rb deleted file mode 100644 index acc26652a..000000000 --- a/spec/unit/parser/methods/collect_spec.rb +++ /dev/null @@ -1,153 +0,0 @@ -require 'puppet' -require 'spec_helper' -require 'puppet_spec/compiler' - -require 'unit/parser/methods/shared' - -describe 'the collect method' do - include PuppetSpec::Compiler - - before :each do - Puppet[:parser] = "future" - end - - context "using future parser" do - context "in Ruby style should be callable as" do - it 'collect on an array (multiplying each value by 2)' do - catalog = compile_to_catalog(<<-MANIFEST) - $a = [1,2,3] - $a.collect {|$x| $x*2}.foreach {|$v| - file { "/file_$v": ensure => present } - } - MANIFEST - - catalog.resource(:file, "/file_2")['ensure'].should == 'present' - catalog.resource(:file, "/file_4")['ensure'].should == 'present' - catalog.resource(:file, "/file_6")['ensure'].should == 'present' - end - - it 'collect on a hash selecting keys' do - catalog = compile_to_catalog(<<-MANIFEST) - $a = {'a'=>1,'b'=>2,'c'=>3} - $a.collect {|$x| $x[0]}.foreach {|$k| - file { "/file_$k": ensure => present } - } - MANIFEST - - catalog.resource(:file, "/file_a")['ensure'].should == 'present' - catalog.resource(:file, "/file_b")['ensure'].should == 'present' - catalog.resource(:file, "/file_c")['ensure'].should == 'present' - end - - it 'foreach on a hash selecting value' do - catalog = compile_to_catalog(<<-MANIFEST) - $a = {'a'=>1,'b'=>2,'c'=>3} - $a.collect {|$x| $x[1]}.foreach {|$k| - file { "/file_$k": ensure => present } - } - MANIFEST - - catalog.resource(:file, "/file_1")['ensure'].should == 'present' - catalog.resource(:file, "/file_2")['ensure'].should == 'present' - catalog.resource(:file, "/file_3")['ensure'].should == 'present' - end - end - - context "handles data type corner cases" do - it "collect gets values that are false" do - catalog = compile_to_catalog(<<-MANIFEST) - $a = [false,false] - $a.collect |$x| { $x }.each |$i, $v| { - file { "/file_$i.$v": ensure => present } - } - MANIFEST - - catalog.resource(:file, "/file_0.false")['ensure'].should == 'present' - catalog.resource(:file, "/file_1.false")['ensure'].should == 'present' - end - - it "collect gets values that are nil" do - Puppet::Parser::Functions.newfunction(:nil_array, :type => :rvalue) do |args| - [nil] - end - catalog = compile_to_catalog(<<-MANIFEST) - $a = nil_array() - $a.collect |$x| { $x }.each |$i, $v| { - file { "/file_$i.$v": ensure => present } - } - MANIFEST - - catalog.resource(:file, "/file_0.")['ensure'].should == 'present' - end - - it "collect gets values that are undef" do - catalog = compile_to_catalog(<<-MANIFEST) - $a = [$does_not_exist] - $a.collect |$x = "something"| { $x }.each |$i, $v| { - file { "/file_$i.$v": ensure => present } - } - MANIFEST - - catalog.resource(:file, "/file_0.")['ensure'].should == 'present' - end - end - - context "in Java style should be callable as" do - shared_examples_for 'java style' do - it 'collect on an array (multiplying each value by 2)' do - catalog = compile_to_catalog(<<-MANIFEST) - $a = [1,2,3] - $a.collect |$x| #{farr}{ $x*2}.foreach |$v| #{farr}{ - file { "/file_$v": ensure => present } - } - MANIFEST - - catalog.resource(:file, "/file_2")['ensure'].should == 'present' - catalog.resource(:file, "/file_4")['ensure'].should == 'present' - catalog.resource(:file, "/file_6")['ensure'].should == 'present' - end - - it 'collect on a hash selecting keys' do - catalog = compile_to_catalog(<<-MANIFEST) - $a = {'a'=>1,'b'=>2,'c'=>3} - $a.collect |$x| #{farr}{ $x[0]}.foreach |$k| #{farr}{ - file { "/file_$k": ensure => present } - } - MANIFEST - - catalog.resource(:file, "/file_a")['ensure'].should == 'present' - catalog.resource(:file, "/file_b")['ensure'].should == 'present' - catalog.resource(:file, "/file_c")['ensure'].should == 'present' - end - - it 'foreach on a hash selecting value' do - catalog = compile_to_catalog(<<-MANIFEST) - $a = {'a'=>1,'b'=>2,'c'=>3} - $a.collect |$x| #{farr} {$x[1]}.foreach |$k|#{farr}{ - file { "/file_$k": ensure => present } - } - MANIFEST - - catalog.resource(:file, "/file_1")['ensure'].should == 'present' - catalog.resource(:file, "/file_2")['ensure'].should == 'present' - catalog.resource(:file, "/file_3")['ensure'].should == 'present' - end - end - - describe 'without fat arrow' do - it_should_behave_like 'java style' do - let(:farr) { '' } - end - end - - describe 'with fat arrow' do - it_should_behave_like 'java style' do - let(:farr) { '=>' } - end - end - end - end - - it_should_behave_like 'all iterative functions argument checks', 'collect' - it_should_behave_like 'all iterative functions hash handling', 'collect' -end diff --git a/spec/unit/parser/methods/each_spec.rb b/spec/unit/parser/methods/each_spec.rb index 4d276e95d..5e9ce4e0c 100644 --- a/spec/unit/parser/methods/each_spec.rb +++ b/spec/unit/parser/methods/each_spec.rb @@ -26,7 +26,7 @@ describe 'the each method' do it 'each on an array selecting each value - function call style' do catalog = compile_to_catalog(<<-MANIFEST) $a = [1,2,3] - each ($a) |$index, $v| => { + each ($a) |$index, $v| { file { "/file_$v": ensure => present } } MANIFEST diff --git a/spec/unit/parser/methods/select_spec.rb b/spec/unit/parser/methods/filter_spec.rb index e61ee3a31..89fb15bbb 100644 --- a/spec/unit/parser/methods/select_spec.rb +++ b/spec/unit/parser/methods/filter_spec.rb @@ -4,17 +4,17 @@ require 'puppet_spec/compiler' require 'unit/parser/methods/shared' -describe 'the select method' do +describe 'the filter method' do include PuppetSpec::Compiler before :each do Puppet[:parser] = 'future' end - it 'should select on an array (all berries)' do + it 'should filter on an array (all berries)' do catalog = compile_to_catalog(<<-MANIFEST) $a = ['strawberry','blueberry','orange'] - $a.select {|$x| $x =~ /berry$/}.foreach {|$v| + $a.filter |$x|{ $x =~ /berry$/}.each |$v|{ file { "/file_$v": ensure => present } } MANIFEST @@ -26,7 +26,7 @@ describe 'the select method' do it 'should produce an array when acting on an array' do catalog = compile_to_catalog(<<-MANIFEST) $a = ['strawberry','blueberry','orange'] - $b = $a.select {|$x| $x =~ /berry$/} + $b = $a.filter |$x|{ $x =~ /berry$/} file { "/file_${b[0]}": ensure => present } file { "/file_${b[1]}": ensure => present } MANIFEST @@ -35,10 +35,10 @@ describe 'the select method' do catalog.resource(:file, "/file_blueberry")['ensure'].should == 'present' end - it 'selects on a hash (all berries) by key' do + it 'filters on a hash (all berries) by key' do catalog = compile_to_catalog(<<-MANIFEST) $a = {'strawberry'=>'red','blueberry'=>'blue','orange'=>'orange'} - $a.select {|$x| $x[0] =~ /berry$/}.foreach {|$v| + $a.filter |$x|{ $x[0] =~ /berry$/}.each |$v|{ file { "/file_${v[0]}": ensure => present } } MANIFEST @@ -50,7 +50,7 @@ describe 'the select method' do it 'should produce a hash when acting on a hash' do catalog = compile_to_catalog(<<-MANIFEST) $a = {'strawberry'=>'red','blueberry'=>'blue','orange'=>'orange'} - $b = $a.select {|$x| $x[0] =~ /berry$/} + $b = $a.filter |$x|{ $x[0] =~ /berry$/} file { "/file_${b['strawberry']}": ensure => present } file { "/file_${b['blueberry']}": ensure => present } file { "/file_${b['orange']}": ensure => present } @@ -62,10 +62,10 @@ describe 'the select method' do catalog.resource(:file, "/file_")['ensure'].should == 'present' end - it 'selects on a hash (all berries) by value' do + it 'filters on a hash (all berries) by value' do catalog = compile_to_catalog(<<-MANIFEST) $a = {'strawb'=>'red berry','blueb'=>'blue berry','orange'=>'orange fruit'} - $a.select {|$x| $x[1] =~ /berry$/}.foreach {|$v| + $a.filter |$x|{ $x[1] =~ /berry$/}.each |$v|{ file { "/file_${v[0]}": ensure => present } } MANIFEST @@ -74,6 +74,6 @@ describe 'the select method' do catalog.resource(:file, "/file_blueb")['ensure'].should == 'present' end - it_should_behave_like 'all iterative functions argument checks', 'select' - it_should_behave_like 'all iterative functions hash handling', 'select' + it_should_behave_like 'all iterative functions argument checks', 'filter' + it_should_behave_like 'all iterative functions hash handling', 'filter' end diff --git a/spec/unit/parser/methods/foreach_spec.rb b/spec/unit/parser/methods/foreach_spec.rb deleted file mode 100755 index 72a9379a4..000000000 --- a/spec/unit/parser/methods/foreach_spec.rb +++ /dev/null @@ -1,91 +0,0 @@ -require 'puppet' -require 'spec_helper' -require 'puppet_spec/compiler' -require 'rubygems' - -describe 'the foreach method' do - include PuppetSpec::Compiler - - before :each do - Puppet[:parser] = 'future' - end - - context "should be callable as" do - it 'foreach on an array selecting each value' do - catalog = compile_to_catalog(<<-MANIFEST) - $a = [1,2,3] - $a.foreach {|$v| - file { "/file_$v": ensure => present } - } - MANIFEST - - catalog.resource(:file, "/file_1")['ensure'].should == 'present' - catalog.resource(:file, "/file_2")['ensure'].should == 'present' - catalog.resource(:file, "/file_3")['ensure'].should == 'present' - end - it 'foreach on an array selecting each value - function call style' do - catalog = compile_to_catalog(<<-MANIFEST) - $a = [1,2,3] - foreach ($a) {|$v| - file { "/file_$v": ensure => present } - } - MANIFEST - - catalog.resource(:file, "/file_1")['ensure'].should == 'present' - catalog.resource(:file, "/file_2")['ensure'].should == 'present' - catalog.resource(:file, "/file_3")['ensure'].should == 'present' - end - - it 'foreach on an array with index' do - catalog = compile_to_catalog(<<-MANIFEST) - $a = [present, absent, present] - $a.foreach {|$k,$v| - file { "/file_${$k+1}": ensure => $v } - } - MANIFEST - - catalog.resource(:file, "/file_1")['ensure'].should == 'present' - catalog.resource(:file, "/file_2")['ensure'].should == 'absent' - catalog.resource(:file, "/file_3")['ensure'].should == 'present' - end - - it 'foreach on a hash selecting entries' do - catalog = compile_to_catalog(<<-MANIFEST) - $a = {'a'=>'present','b'=>'absent','c'=>'present'} - $a.foreach {|$e| - file { "/file_${e[0]}": ensure => $e[1] } - } - MANIFEST - - catalog.resource(:file, "/file_a")['ensure'].should == 'present' - catalog.resource(:file, "/file_b")['ensure'].should == 'absent' - catalog.resource(:file, "/file_c")['ensure'].should == 'present' - end - it 'foreach on a hash selecting key and value' do - catalog = compile_to_catalog(<<-MANIFEST) - $a = {'a'=>present,'b'=>absent,'c'=>present} - $a.foreach {|$k, $v| - file { "/file_$k": ensure => $v } - } - MANIFEST - - catalog.resource(:file, "/file_a")['ensure'].should == 'present' - catalog.resource(:file, "/file_b")['ensure'].should == 'absent' - catalog.resource(:file, "/file_c")['ensure'].should == 'present' - end - end - context "should produce receiver" do - it 'each checking produced value using single expression' do - catalog = compile_to_catalog(<<-MANIFEST) - $a = [1, 3, 2] - $b = $a.each |$x| { $x } - file { "/file_${b[1]}": - ensure => present - } - MANIFEST - - catalog.resource(:file, "/file_3")['ensure'].should == 'present' - end - - end -end diff --git a/spec/unit/parser/methods/map_spec.rb b/spec/unit/parser/methods/map_spec.rb new file mode 100644 index 000000000..025501754 --- /dev/null +++ b/spec/unit/parser/methods/map_spec.rb @@ -0,0 +1,95 @@ +require 'puppet' +require 'spec_helper' +require 'puppet_spec/compiler' + +require 'unit/parser/methods/shared' + +describe 'the map method' do + include PuppetSpec::Compiler + + before :each do + Puppet[:parser] = "future" + end + + context "using future parser" do + it 'map on an array (multiplying each value by 2)' do + catalog = compile_to_catalog(<<-MANIFEST) + $a = [1,2,3] + $a.map |$x|{ $x*2}.each |$v|{ + file { "/file_$v": ensure => present } + } + MANIFEST + + catalog.resource(:file, "/file_2")['ensure'].should == 'present' + catalog.resource(:file, "/file_4")['ensure'].should == 'present' + catalog.resource(:file, "/file_6")['ensure'].should == 'present' + end + + it 'map on a hash selecting keys' do + catalog = compile_to_catalog(<<-MANIFEST) + $a = {'a'=>1,'b'=>2,'c'=>3} + $a.map |$x|{ $x[0]}.each |$k|{ + file { "/file_$k": ensure => present } + } + MANIFEST + + catalog.resource(:file, "/file_a")['ensure'].should == 'present' + catalog.resource(:file, "/file_b")['ensure'].should == 'present' + catalog.resource(:file, "/file_c")['ensure'].should == 'present' + end + + it 'each on a hash selecting value' do + catalog = compile_to_catalog(<<-MANIFEST) + $a = {'a'=>1,'b'=>2,'c'=>3} + $a.map |$x|{ $x[1]}.each |$k|{ + file { "/file_$k": ensure => present } + } + MANIFEST + + catalog.resource(:file, "/file_1")['ensure'].should == 'present' + catalog.resource(:file, "/file_2")['ensure'].should == 'present' + catalog.resource(:file, "/file_3")['ensure'].should == 'present' + end + + context "handles data type corner cases" do + it "map gets values that are false" do + catalog = compile_to_catalog(<<-MANIFEST) + $a = [false,false] + $a.map |$x| { $x }.each |$i, $v| { + file { "/file_$i.$v": ensure => present } + } + MANIFEST + + catalog.resource(:file, "/file_0.false")['ensure'].should == 'present' + catalog.resource(:file, "/file_1.false")['ensure'].should == 'present' + end + + it "map gets values that are nil" do + Puppet::Parser::Functions.newfunction(:nil_array, :type => :rvalue) do |args| + [nil] + end + catalog = compile_to_catalog(<<-MANIFEST) + $a = nil_array() + $a.map |$x| { $x }.each |$i, $v| { + file { "/file_$i.$v": ensure => present } + } + MANIFEST + + catalog.resource(:file, "/file_0.")['ensure'].should == 'present' + end + + it "map gets values that are undef" do + catalog = compile_to_catalog(<<-MANIFEST) + $a = [$does_not_exist] + $a.map |$x = "something"| { $x }.each |$i, $v| { + file { "/file_$i.$v": ensure => present } + } + MANIFEST + + catalog.resource(:file, "/file_0.")['ensure'].should == 'present' + end + end + it_should_behave_like 'all iterative functions argument checks', 'map' + it_should_behave_like 'all iterative functions hash handling', 'map' + end +end diff --git a/spec/unit/parser/methods/reduce_spec.rb b/spec/unit/parser/methods/reduce_spec.rb index 99ecfd18d..5d4549b54 100644 --- a/spec/unit/parser/methods/reduce_spec.rb +++ b/spec/unit/parser/methods/reduce_spec.rb @@ -28,40 +28,41 @@ describe 'the reduce method' do it 'reduce on an array' do catalog = compile_to_catalog(<<-MANIFEST) $a = [1,2,3] - $b = $a.reduce {|$memo, $x| $memo + $x } + $b = $a.reduce |$memo, $x| { $memo + $x } file { "/file_$b": ensure => present } MANIFEST catalog.resource(:file, "/file_6")['ensure'].should == 'present' - end + end + it 'reduce on an array with start value' do catalog = compile_to_catalog(<<-MANIFEST) $a = [1,2,3] - $b = $a.reduce(4) {|$memo, $x| $memo + $x } + $b = $a.reduce(4) |$memo, $x| { $memo + $x } file { "/file_$b": ensure => present } MANIFEST - + catalog.resource(:file, "/file_10")['ensure'].should == 'present' - end + end it 'reduce on a hash' do catalog = compile_to_catalog(<<-MANIFEST) $a = {a=>1, b=>2, c=>3} $start = [ignored, 4] - $b = $a.reduce {|$memo, $x| ['sum', $memo[1] + $x[1]] } + $b = $a.reduce |$memo, $x| {['sum', $memo[1] + $x[1]] } file { "/file_${$b[0]}_${$b[1]}": ensure => present } MANIFEST - + catalog.resource(:file, "/file_sum_6")['ensure'].should == 'present' - end + end it 'reduce on a hash with start value' do catalog = compile_to_catalog(<<-MANIFEST) $a = {a=>1, b=>2, c=>3} $start = ['ignored', 4] - $b = $a.reduce($start) {|$memo, $x| ['sum', $memo[1] + $x[1]] } + $b = $a.reduce($start) |$memo, $x| { ['sum', $memo[1] + $x[1]] } file { "/file_${$b[0]}_${$b[1]}": ensure => present } MANIFEST - + catalog.resource(:file, "/file_sum_10")['ensure'].should == 'present' - end + end end end diff --git a/spec/unit/parser/methods/reject_spec.rb b/spec/unit/parser/methods/reject_spec.rb deleted file mode 100644 index e37eaebc5..000000000 --- a/spec/unit/parser/methods/reject_spec.rb +++ /dev/null @@ -1,73 +0,0 @@ -require 'puppet' -require 'spec_helper' -require 'puppet_spec/compiler' - -require 'unit/parser/methods/shared' - -describe 'the reject method' do - include PuppetSpec::Compiler - - before :each do - Puppet[:parser] = 'future' - end - - it 'rejects on an array (no berries)' do - catalog = compile_to_catalog(<<-MANIFEST) - $a = ['strawberry','blueberry','orange'] - $a.reject {|$x| $x =~ /berry$/}.foreach {|$v| - file { "/file_$v": ensure => present } - } - MANIFEST - - catalog.resource(:file, "/file_orange")['ensure'].should == 'present' - catalog.resource(:file, "/file_strawberry").should == nil - end - - it 'produces an array when acting on an array' do - catalog = compile_to_catalog(<<-MANIFEST) - $a = ['strawberry','blueberry','orange'] - $b = $a.reject {|$x| $x =~ /berry$/} - file { "/file_${b[0]}": ensure => present } - - MANIFEST - - catalog.resource(:file, "/file_orange")['ensure'].should == 'present' - catalog.resource(:file, "/file_strawberry").should == nil - end - - it 'rejects on a hash (all berries) by key' do - catalog = compile_to_catalog(<<-MANIFEST) - $a = {'strawberry'=>'red','blueberry'=>'blue','orange'=>'orange'} - $a.reject {|$x| $x[0] =~ /berry$/}.foreach {|$v| - file { "/file_${v[0]}": ensure => present } - } - MANIFEST - - catalog.resource(:file, "/file_orange")['ensure'].should == 'present' - end - - it 'produces a hash when acting on a hash' do - catalog = compile_to_catalog(<<-MANIFEST) - $a = {'strawberry'=>'red','blueberry'=>'blue','grape'=>'purple'} - $b = $a.reject {|$x| $x[0] =~ /berry$/} - file { "/file_${b[grape]}": ensure => present } - - MANIFEST - - catalog.resource(:file, "/file_purple")['ensure'].should == 'present' - end - - it 'rejects on a hash (all berries) by value' do - catalog = compile_to_catalog(<<-MANIFEST) - $a = {'strawb'=>'red berry','blueb'=>'blue berry','orange'=>'orange fruit'} - $a.reject {|$x| $x[1] =~ /berry$/}.foreach {|$v| - file { "/file_${v[0]}": ensure => present } - } - MANIFEST - - catalog.resource(:file, "/file_orange")['ensure'].should == 'present' - end - - it_should_behave_like 'all iterative functions argument checks', 'reject' - it_should_behave_like 'all iterative functions hash handling', 'reject' -end diff --git a/spec/unit/parser/methods/shared.rb b/spec/unit/parser/methods/shared.rb index bf9c9f05f..704769d83 100644 --- a/spec/unit/parser/methods/shared.rb +++ b/spec/unit/parser/methods/shared.rb @@ -2,7 +2,7 @@ shared_examples_for 'all iterative functions hash handling' do |func| it 'passes a hash entry as an array of the key and value' do catalog = compile_to_catalog(<<-MANIFEST) - {a=>1}.#{func} { |$v| notify { "${v[0]} ${v[1]}": } } + {a=>1}.#{func} |$v| { notify { "${v[0]} ${v[1]}": } } MANIFEST catalog.resource(:notify, "a 1").should_not be_nil @@ -14,7 +14,7 @@ shared_examples_for 'all iterative functions argument checks' do |func| it 'raises an error when defined with more than 1 argument' do expect do compile_to_catalog(<<-MANIFEST) - [1].#{func} { |$x, $yikes| } + [1].#{func} |$x, $yikes|{ } MANIFEST end.to raise_error(Puppet::Error, /Too few arguments/) end @@ -22,7 +22,7 @@ shared_examples_for 'all iterative functions argument checks' do |func| it 'raises an error when defined with fewer than 1 argument' do expect do compile_to_catalog(<<-MANIFEST) - [1].#{func} { || } + [1].#{func} || { } MANIFEST end.to raise_error(Puppet::Error, /Too many arguments/) end @@ -30,7 +30,7 @@ shared_examples_for 'all iterative functions argument checks' do |func| it 'raises an error when used against an unsupported type' do expect do compile_to_catalog(<<-MANIFEST) - "not correct".#{func} { |$v| } + "not correct".#{func} |$v| { } MANIFEST end.to raise_error(Puppet::Error, /must be an Array or a Hash/) end @@ -38,7 +38,7 @@ shared_examples_for 'all iterative functions argument checks' do |func| it 'raises an error when called with any parameters besides a block' do expect do compile_to_catalog(<<-MANIFEST) - [1].#{func}(1) { |$v| } + [1].#{func}(1) |$v| { } MANIFEST end.to raise_error(Puppet::Error, /Wrong number of arguments/) end diff --git a/spec/unit/parser/methods/slice_spec.rb b/spec/unit/parser/methods/slice_spec.rb index b213415a1..1069bc75a 100644 --- a/spec/unit/parser/methods/slice_spec.rb +++ b/spec/unit/parser/methods/slice_spec.rb @@ -27,15 +27,15 @@ describe 'methods' do end context "should be callable on array as" do - + it 'slice with explicit parameters' do catalog = compile_to_catalog(<<-MANIFEST) $a = [1, present, 2, absent, 3, present] - $a.slice(2) |$k,$v| { + $a.slice(2) |$k,$v| { file { "/file_${$k}": ensure => $v } } MANIFEST - + catalog.resource(:file, "/file_1")['ensure'].should == 'present' catalog.resource(:file, "/file_2")['ensure'].should == 'absent' catalog.resource(:file, "/file_3")['ensure'].should == 'present' @@ -43,11 +43,11 @@ describe 'methods' do it 'slice with one parameter' do catalog = compile_to_catalog(<<-MANIFEST) $a = [1, present, 2, absent, 3, present] - $a.slice(2) |$k| { + $a.slice(2) |$k| { file { "/file_${$k[0]}": ensure => $k[1] } } MANIFEST - + catalog.resource(:file, "/file_1")['ensure'].should == 'present' catalog.resource(:file, "/file_2")['ensure'].should == 'absent' catalog.resource(:file, "/file_3")['ensure'].should == 'present' @@ -55,39 +55,39 @@ describe 'methods' do it 'slice with shorter last slice' do catalog = compile_to_catalog(<<-MANIFEST) $a = [1, present, 2, present, 3, absent] - $a.slice(4) |$a, $b, $c, $d| { + $a.slice(4) |$a, $b, $c, $d| { file { "/file_$a.$c": ensure => $b } } MANIFEST - + catalog.resource(:file, "/file_1.2")['ensure'].should == 'present' catalog.resource(:file, "/file_3.")['ensure'].should == 'absent' end end context "should be callable on hash as" do - + it 'slice with explicit parameters, missing are empty' do catalog = compile_to_catalog(<<-MANIFEST) $a = {1=>present, 2=>present, 3=>absent} - $a.slice(2) |$a,$b| { + $a.slice(2) |$a,$b| { file { "/file_${a[0]}.${b[0]}": ensure => $a[1] } } MANIFEST - + catalog.resource(:file, "/file_1.2")['ensure'].should == 'present' catalog.resource(:file, "/file_3.")['ensure'].should == 'absent' end - + end context "when called without a block" do it "should produce an array with the result" do catalog = compile_to_catalog(<<-MANIFEST) $a = [1, present, 2, absent, 3, present] - $a.slice(2).each |$k| { + $a.slice(2).each |$k| { file { "/file_${$k[0]}": ensure => $k[1] } } MANIFEST - + catalog.resource(:file, "/file_1")['ensure'].should == 'present' catalog.resource(:file, "/file_2")['ensure'].should == 'absent' catalog.resource(:file, "/file_3")['ensure'].should == 'present' diff --git a/spec/unit/parser/parser_spec.rb b/spec/unit/parser/parser_spec.rb index 38a5c7a85..2c4cf50df 100755 --- a/spec/unit/parser/parser_spec.rb +++ b/spec/unit/parser/parser_spec.rb @@ -53,7 +53,7 @@ describe Puppet::Parser do describe "when parsing files" do before do - FileTest.stubs(:exist?).returns true + Puppet::FileSystem::File.stubs(:exist?).returns true File.stubs(:read).returns "" @parser.stubs(:watch_file) end diff --git a/spec/unit/parser/resource/param_spec.rb b/spec/unit/parser/resource/param_spec.rb new file mode 100755 index 000000000..7989d060d --- /dev/null +++ b/spec/unit/parser/resource/param_spec.rb @@ -0,0 +1,44 @@ +#! /usr/bin/env ruby +require 'spec_helper' + +describe Puppet::Parser::Resource::Param do + it "can be instantiated" do + Puppet::Parser::Resource::Param.new(:name => 'myparam', :value => 'foo') + end + + it "stores the source file" do + param = Puppet::Parser::Resource::Param.new(:name => 'myparam', :value => 'foo', :file => 'foo.pp') + param.file.should == 'foo.pp' + end + + it "stores the line number" do + param = Puppet::Parser::Resource::Param.new(:name => 'myparam', :value => 'foo', :line => 42) + param.line.should == 42 + end + + context "parameter validation" do + it "throws an error when instantiated without a name" do + expect { + Puppet::Parser::Resource::Param.new(:value => 'foo') + }.to raise_error(Puppet::Error, /name is a required option/) + end + + it "throws an error when instantiated without a value" do + expect { + Puppet::Parser::Resource::Param.new(:name => 'myparam') + }.to raise_error(Puppet::Error, /value is a required option/) + end + + it "throws an error when instantiated with a nil value" do + expect { + Puppet::Parser::Resource::Param.new(:name => 'myparam', :value => nil) + }.to raise_error(Puppet::Error, /value is a required option/) + end + + it "includes file/line context in errors" do + expect { + Puppet::Parser::Resource::Param.new(:name => 'myparam', :value => nil, :file => 'foo.pp', :line => 42) + }.to raise_error(Puppet::Error, /foo.pp:42/) + end + end +end diff --git a/spec/unit/parser/resource_spec.rb b/spec/unit/parser/resource_spec.rb index 221efb83b..74a66d1c1 100755 --- a/spec/unit/parser/resource_spec.rb +++ b/spec/unit/parser/resource_spec.rb @@ -90,7 +90,7 @@ describe Puppet::Parser::Resource do it "should use a Puppet::Resource for converting to a ral resource" do trans = mock 'resource', :to_ral => "yay" @resource = mkresource - @resource.expects(:to_resource).returns trans + @resource.expects(:copy_as_resource).returns trans @resource.to_ral.should == "yay" end @@ -124,7 +124,8 @@ describe Puppet::Parser::Resource do tags = [ "tag1", "tag2" ] @arguments[:parameters] = [ param(:tag, tags , :source) ] res = Puppet::Parser::Resource.new("resource", "testing", @arguments) - (res.tags & tags).should == tags + res.should be_tagged("tag1") + res.should be_tagged("tag2") end end @@ -445,7 +446,7 @@ describe Puppet::Parser::Resource do 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(:to_resource) + @resource.should respond_to(:copy_as_resource) end describe "when being converted to a resource" do @@ -454,19 +455,19 @@ describe Puppet::Parser::Resource do end it "should create an instance of Puppet::Resource" do - @parser_resource.to_resource.should be_instance_of(Puppet::Resource) + @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.to_resource.type.should == @parser_resource.type + @parser_resource.copy_as_resource.type.should == @parser_resource.type end it "should set the title correctly on the Puppet::Resource" do - @parser_resource.to_resource.title.should == @parser_resource.title + @parser_resource.copy_as_resource.title.should == @parser_resource.title end it "should copy over all of the parameters" do - result = @parser_resource.to_resource.to_hash + result = @parser_resource.copy_as_resource.to_hash # The name will be in here, also. result[:foo].should == "bar" @@ -477,40 +478,40 @@ describe Puppet::Parser::Resource do @parser_resource.tag "foo" @parser_resource.tag "bar" - @parser_resource.to_resource.tags.should == @parser_resource.tags + @parser_resource.copy_as_resource.tags.should == @parser_resource.tags end it "should copy over the line" do @parser_resource.line = 40 - @parser_resource.to_resource.line.should == 40 + @parser_resource.copy_as_resource.line.should == 40 end it "should copy over the file" do @parser_resource.file = "/my/file" - @parser_resource.to_resource.file.should == "/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.to_resource.exported.should be_true + @parser_resource.copy_as_resource.exported.should be_true end it "should copy over the 'virtual' value" do @parser_resource.virtual = true - @parser_resource.to_resource.virtual.should be_true + @parser_resource.copy_as_resource.virtual.should be_true 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.to_resource + 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.to_resource + result = @parser_resource.copy_as_resource result[:fee].should == ["a", Puppet::Resource.new(:file, "/my/file")] end @@ -518,7 +519,7 @@ describe Puppet::Parser::Resource 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.to_resource + result = @parser_resource.copy_as_resource result[:fee].should == ["a", Puppet::Resource.new(:file, "/my/file1"), Puppet::Resource.new(:file, "/my/file2")] end diff --git a/spec/unit/pops/model/ast_transformer_spec.rb b/spec/unit/pops/model/ast_transformer_spec.rb index f2b1c9b56..969e944de 100644 --- a/spec/unit/pops/model/ast_transformer_spec.rb +++ b/spec/unit/pops/model/ast_transformer_spec.rb @@ -13,28 +13,42 @@ describe Puppet::Pops::Model::AstTransformer do it "converts a decimal number to a string Name" do ast = transform(QNAME_OR_NUMBER("10")) - ast.should be_kind_of Puppet::Parser::AST::Name + ast.should be_kind_of(Puppet::Parser::AST::Name) ast.value.should == "10" end + it "converts a 0 to a decimal 0" do + ast = transform(QNAME_OR_NUMBER("0")) + + ast.should be_kind_of(Puppet::Parser::AST::Name) + ast.value.should == "0" + end + + it "converts a 00 to an octal 00" do + ast = transform(QNAME_OR_NUMBER("0")) + + ast.should be_kind_of(Puppet::Parser::AST::Name) + ast.value.should == "0" + end + it "converts an octal number to a string Name" do ast = transform(QNAME_OR_NUMBER("020")) - ast.should be_kind_of Puppet::Parser::AST::Name + ast.should be_kind_of(Puppet::Parser::AST::Name) ast.value.should == "020" end it "converts a hex number to a string Name" do ast = transform(QNAME_OR_NUMBER("0x20")) - ast.should be_kind_of Puppet::Parser::AST::Name + ast.should be_kind_of(Puppet::Parser::AST::Name) ast.value.should == "0x20" end it "converts an unknown radix to an error string" do ast = transform(Puppet::Pops::Model::Factory.new(Puppet::Pops::Model::LiteralNumber, 3, 2)) - ast.should be_kind_of Puppet::Parser::AST::Name + ast.should be_kind_of(Puppet::Parser::AST::Name) ast.value.should == "bad radix:3" end end diff --git a/spec/unit/pops/parser/lexer_spec.rb b/spec/unit/pops/parser/lexer_spec.rb index 985807261..71010c033 100755 --- a/spec/unit/pops/parser/lexer_spec.rb +++ b/spec/unit/pops/parser/lexer_spec.rb @@ -154,7 +154,7 @@ describe Puppet::Pops::Parser::Lexer::TOKENS do :LBRACK => '[', :RBRACK => ']', # :LBRACE => '{', - :RBRACE => '}', +# :RBRACE => '}', :LPAREN => '(', :RPAREN => ')', :EQUALS => '=', @@ -232,7 +232,7 @@ describe Puppet::Pops::Parser::Lexer::TOKENS do # These tokens' strings don't matter, just that the tokens exist. [:STRING, :DQPRE, :DQMID, :DQPOST, :BOOLEAN, :NAME, :NUMBER, :COMMENT, :MLCOMMENT, - :LBRACE, :LAMBDA, + :LBRACE, :RBRACE, :RETURN, :SQUOTE, :DQUOTE, :VARIABLE].each do |name| it "should have a token named #{name.to_s}" do Puppet::Pops::Parser::Lexer::TOKENS[name].should_not be_nil @@ -583,7 +583,24 @@ describe Puppet::Pops::Parser::Lexer,"when lexing strings" do %q[""] => [[:STRING,""]], %q["123 456 789 0"] => [[:STRING,"123 456 789 0"]], %q["${123} 456 $0"] => [[:DQPRE,""],[:VARIABLE,"123"],[:DQMID," 456 "],[:VARIABLE,"0"],[:DQPOST,""]], - %q["$foo::::bar"] => [[:DQPRE,""],[:VARIABLE,"foo"],[:DQPOST,"::::bar"]] + %q["$foo::::bar"] => [[:DQPRE,""],[:VARIABLE,"foo"],[:DQPOST,"::::bar"]], + # Keyword variables + %q["$true"] => [[:DQPRE,""],[:VARIABLE, "true"],[:DQPOST,""]], + %q["$false"] => [[:DQPRE,""],[:VARIABLE, "false"],[:DQPOST,""]], + %q["$if"] => [[:DQPRE,""],[:VARIABLE, "if"],[:DQPOST,""]], + %q["$case"] => [[:DQPRE,""],[:VARIABLE, "case"],[:DQPOST,""]], + %q["$unless"] => [[:DQPRE,""],[:VARIABLE, "unless"],[:DQPOST,""]], + %q["$undef"] => [[:DQPRE,""],[:VARIABLE, "undef"],[:DQPOST,""]], + # Expressions + %q["${true}"] => [[:DQPRE,""],[:BOOLEAN, true],[:DQPOST,""]], + %q["${false}"] => [[:DQPRE,""],[:BOOLEAN, false],[:DQPOST,""]], + %q["${undef}"] => [[:DQPRE,""],:UNDEF,[:DQPOST,""]], + %q["${if true {false}}"] => [[:DQPRE,""],:IF,[:BOOLEAN, true], :LBRACE, [:BOOLEAN, false], :RBRACE, [:DQPOST,""]], + %q["${unless true {false}}"] => [[:DQPRE,""],:UNLESS,[:BOOLEAN, true], :LBRACE, [:BOOLEAN, false], :RBRACE, [:DQPOST,""]], + %q["${case true {true:{false}}}"] => [ + [:DQPRE,""],:CASE,[:BOOLEAN, true], :LBRACE, [:BOOLEAN, true], :COLON, :LBRACE, [:BOOLEAN, false], + :RBRACE, :RBRACE, [:DQPOST,""]], + %q[{ "${a}" => 1 }] => [ :LBRACE, [:DQPRE,""], [:VARIABLE,"a"], [:DQPOST,""], :FARROW, [:NAME,"1"], :RBRACE ], }.each { |src,expected_result| it "should handle #{src} correctly" do EgrammarLexerSpec.tokens_scanned_from(src).should be_like(*expected_result) @@ -742,7 +759,7 @@ describe "Puppet::Pops::Parser::Lexer in the old tests" do it "should end variables at `-`" do EgrammarLexerSpec.tokens_scanned_from('$hyphenated-variable'). - should be_like [:VARIABLE, "hyphenated"], [:MINUS, '-'], [:NAME, 'variable'] + should be_like([:VARIABLE, "hyphenated"], [:MINUS, '-'], [:NAME, 'variable']) end it "should not include whitespace in a variable" do @@ -769,7 +786,7 @@ describe "when trying to lex a non-existent file" do it "should return an empty list of tokens" do lexer = Puppet::Pops::Parser::Lexer.new lexer.file = nofile = tmpfile('lexer') - File.exists?(nofile).should == false + Puppet::FileSystem::File.exist?(nofile).should == false lexer.fullscan.should == [[false,false]] end diff --git a/spec/unit/pops/parser/parse_calls_spec.rb b/spec/unit/pops/parser/parse_calls_spec.rb index 647d0e16c..800a15bec 100644 --- a/spec/unit/pops/parser/parse_calls_spec.rb +++ b/spec/unit/pops/parser/parse_calls_spec.rb @@ -81,16 +81,16 @@ describe "egrammar parsing function calls" do dump(parse("$a.foo")).should == "(call-method (. $a foo))" end - it "$a.foo {|| }" do + it "$a.foo || { }" do dump(parse("$a.foo || { }")).should == "(call-method (. $a foo) (lambda ()))" end - it "$a.foo {|$x| }" do - dump(parse("$a.foo {|$x| }")).should == "(call-method (. $a foo) (lambda (parameters x) ()))" + it "$a.foo |$x| { }" do + dump(parse("$a.foo |$x|{ }")).should == "(call-method (. $a foo) (lambda (parameters x) ()))" end - it "$a.foo {|$x| }" do - dump(parse("$a.foo {|$x| $b = $x}")).should == + it "$a.foo |$x|{ }" do + dump(parse("$a.foo |$x|{ $b = $x}")).should == "(call-method (. $a foo) (lambda (parameters x) (block (= $b $x))))" end end diff --git a/spec/unit/pops/transformer/transform_calls_spec.rb b/spec/unit/pops/transformer/transform_calls_spec.rb index f2303db5b..969a2d7b8 100644 --- a/spec/unit/pops/transformer/transform_calls_spec.rb +++ b/spec/unit/pops/transformer/transform_calls_spec.rb @@ -60,20 +60,20 @@ describe "transformation to Puppet AST for function calls" do astdump(parse("$a.foo")).should == "(call-method (. $a foo))" end - it "$a.foo {|| }" do + it "$a.foo ||{ }" do astdump(parse("$a.foo || { }")).should == "(call-method (. $a foo) (lambda ()))" end - it "$a.foo {|| []} # check transformation to block with empty array" do - astdump(parse("$a.foo || { []}")).should == "(call-method (. $a foo) (lambda (block ([]))))" + it "$a.foo ||{[]} # check transformation to block with empty array" do + astdump(parse("$a.foo || {[]}")).should == "(call-method (. $a foo) (lambda (block ([]))))" end it "$a.foo {|$x| }" do - astdump(parse("$a.foo {|$x| }")).should == "(call-method (. $a foo) (lambda (parameters x) ()))" + astdump(parse("$a.foo |$x| { }")).should == "(call-method (. $a foo) (lambda (parameters x) ()))" end - it "$a.foo {|$x| $b = $x}" do - astdump(parse("$a.foo {|$x| $b = $x}")).should == + it "$a.foo |$x| { $b = $x}" do + astdump(parse("$a.foo |$x| { $b = $x}")).should == "(call-method (. $a foo) (lambda (parameters x) (block (= $b $x))))" end end diff --git a/spec/unit/pops/transformer/transform_containers_spec.rb b/spec/unit/pops/transformer/transform_containers_spec.rb index 682860fe1..8c65e2bcc 100644 --- a/spec/unit/pops/transformer/transform_containers_spec.rb +++ b/spec/unit/pops/transformer/transform_containers_spec.rb @@ -154,12 +154,12 @@ describe "transformation to Puppet AST for containers" do it "node foo inherits 'bar' {}" do # AST can not differentiate between bare word and string - astdump(parse("node foo inherits 'bar' {}")).should == "(node (matches 'foo') (parent 'bar') ())" + astdump(parse("node foo inherits 'bar' {}")).should == "(node (matches 'foo') (parent bar) ())" end it "node foo inherits default {}" do # AST can not differentiate between bare word and string - astdump(parse("node foo inherits default {}")).should == "(node (matches 'foo') (parent :default) ())" + astdump(parse("node foo inherits default {}")).should == "(node (matches 'foo') (parent default) ())" end it "node /web.*/ {}" do diff --git a/spec/unit/pops/validator/validator_spec.rb b/spec/unit/pops/validator/validator_spec.rb new file mode 100644 index 000000000..d54d2ca61 --- /dev/null +++ b/spec/unit/pops/validator/validator_spec.rb @@ -0,0 +1,31 @@ +#! /usr/bin/env ruby +require 'spec_helper' +require 'puppet/pops' +require 'puppet_spec/pops' + +# relative to this spec file (./) does not work as this file is loaded by rspec +require File.join(File.dirname(__FILE__), '../parser/parser_rspec_helper') + +describe "validating 3x" do + include ParserRspecHelper + include PuppetSpec::Pops + + let(:acceptor) { Puppet::Pops::Validation::Acceptor.new() } + let(:validator) { Puppet::Pops::Validation::ValidatorFactory_3_1.new().validator(acceptor) } + + def validate(model) + validator.validate(model) + acceptor + end + + it 'should raise error for illegal names' do + expect(validate(fqn('Aaa'))).to have_issue(Puppet::Pops::Issues::ILLEGAL_NAME) + expect(validate(fqn('AAA'))).to have_issue(Puppet::Pops::Issues::ILLEGAL_NAME) + end + + it 'should raise error for illegal variable names' do + expect(validate(fqn('Aaa').var())).to have_issue(Puppet::Pops::Issues::ILLEGAL_NAME) + expect(validate(fqn('AAA').var())).to have_issue(Puppet::Pops::Issues::ILLEGAL_NAME) + end + +end
\ No newline at end of file diff --git a/spec/unit/provider/augeas/augeas_spec.rb b/spec/unit/provider/augeas/augeas_spec.rb index e75a0d36e..9b6e88ce8 100755 --- a/spec/unit/provider/augeas/augeas_spec.rb +++ b/spec/unit/provider/augeas/augeas_spec.rb @@ -276,6 +276,61 @@ describe provider_class do @provider.need_to_run?.should == true end + describe "performing numeric comparisons (#22617)" do + it "should return true when a get string compare is true" do + @resource[:onlyif] = "get bpath > a" + @augeas.stubs("get").returns("b") + @provider.need_to_run?.should == true + end + + it "should return false when a get string compare is false" do + @resource[:onlyif] = "get a19path > a2" + @augeas.stubs("get").returns("a19") + @provider.need_to_run?.should == false + end + + it "should return true when a get int gt compare is true" do + @resource[:onlyif] = "get path19 > 2" + @augeas.stubs("get").returns("19") + @provider.need_to_run?.should == true + end + + it "should return true when a get int ge compare is true" do + @resource[:onlyif] = "get path19 >= 2" + @augeas.stubs("get").returns("19") + @provider.need_to_run?.should == true + end + + it "should return true when a get int lt compare is true" do + @resource[:onlyif] = "get path2 < 19" + @augeas.stubs("get").returns("2") + @provider.need_to_run?.should == true + end + + it "should return false when a get int le compare is false" do + @resource[:onlyif] = "get path39 <= 4" + @augeas.stubs("get").returns("39") + @provider.need_to_run?.should == false + end + end + describe "performing is_numeric checks (#22617)" do + it "should return false for nil" do + @provider.is_numeric?(nil).should == false + end + it "should return true for Fixnums" do + @provider.is_numeric?(9).should == true + end + it "should return true for numbers in Strings" do + @provider.is_numeric?('9').should == true + end + it "should return false for non-number Strings" do + @provider.is_numeric?('x9').should == false + end + it "should return false for other types" do + @provider.is_numeric?([true]).should == false + end + end + it "should return false when a get filter does not match" do @resource[:onlyif] = "get path == another value" @augeas.stubs("get").returns("value") @@ -619,7 +674,7 @@ describe provider_class do link = tmpfile('link') target = tmpfile('target') FileUtils.touch(target) - FileUtils.symlink(target, link) + Puppet::FileSystem::File.new(target).symlink(link) resource = Puppet::Type.type(:augeas).new( :name => 'test', @@ -634,7 +689,7 @@ describe provider_class do catalog.apply File.ftype(link).should == 'link' - File.readlink(link).should == target + Puppet::FileSystem::File.new(link).readlink().should == target File.read(target).should =~ /PermitRootLogin no/ end end diff --git a/spec/unit/provider/exec/posix_spec.rb b/spec/unit/provider/exec/posix_spec.rb index 39cc10b41..7c4982fcc 100755 --- a/spec/unit/provider/exec/posix_spec.rb +++ b/spec/unit/provider/exec/posix_spec.rb @@ -64,7 +64,7 @@ describe Puppet::Type.type(:exec).provider(:posix) do provider.resource[:path] = [File.dirname(command)] filename = File.basename(command) - Puppet::Util::Execution.expects(:execute).with { |cmdline, arguments| (cmdline == filename) && (arguments.is_a? Hash) } + Puppet::Util::Execution.expects(:execute).with { |cmdline, arguments| (cmdline == filename) && (arguments.is_a? Hash) }.returns(Puppet::Util::Execution::ProcessOutput.new('', 0)) provider.run(filename) end @@ -95,7 +95,7 @@ describe Puppet::Type.type(:exec).provider(:posix) do provider.resource[:path] = ['/bogus/bin'] command = make_exe - Puppet::Util::Execution.expects(:execute).with { |cmdline, arguments| (cmdline == "#{command} bar --sillyarg=true --blah") && (arguments.is_a? Hash) } + Puppet::Util::Execution.expects(:execute).with { |cmdline, arguments| (cmdline == "#{command} bar --sillyarg=true --blah") && (arguments.is_a? Hash) }.returns(Puppet::Util::Execution::ProcessOutput.new('', 0)) provider.run("#{command} bar --sillyarg=true --blah") end @@ -110,11 +110,16 @@ describe Puppet::Type.type(:exec).provider(:posix) do provider.resource[:environment] = ['WHATEVER=/something/else', 'WHATEVER=/foo'] command = make_exe - Puppet::Util::Execution.expects(:execute).with { |cmdline, arguments| (cmdline == command) && (arguments.is_a? Hash) } + Puppet::Util::Execution.expects(:execute).with { |cmdline, arguments| (cmdline == command) && (arguments.is_a? Hash) }.returns(Puppet::Util::Execution::ProcessOutput.new('', 0)) provider.run(command) @logs.map {|l| "#{l.level}: #{l.message}" }.should == ["warning: Overriding environment setting 'WHATEVER' with '/foo'"] end + it "should set umask before execution if umask parameter is in use" do + provider.resource[:umask] = '0027' + Puppet::Util.expects(:withumask).with(0027) + provider.run(provider.resource[:command]) + end describe "posix locale settings", :unless => Puppet.features.microsoft_windows? do # a sentinel value that we can use to emulate what locale environment variables might be set to on an international diff --git a/spec/unit/provider/file/posix_spec.rb b/spec/unit/provider/file/posix_spec.rb index e84d23a5a..48eaae30a 100755 --- a/spec/unit/provider/file/posix_spec.rb +++ b/spec/unit/provider/file/posix_spec.rb @@ -85,7 +85,7 @@ describe Puppet::Type.type(:file).provider(:posix), :if => Puppet.features.posix describe "#owner" do it "should return the uid of the file owner" do FileUtils.touch(path) - owner = File.stat(path).uid + owner = Puppet::FileSystem::File.new(path).stat.uid provider.owner.should == owner end @@ -178,7 +178,7 @@ describe Puppet::Type.type(:file).provider(:posix), :if => Puppet.features.posix describe "#group" do it "should return the gid of the file group" do FileUtils.touch(path) - group = File.stat(path).gid + group = Puppet::FileSystem::File.new(path).stat.gid provider.group.should == group end diff --git a/spec/unit/provider/group/windows_adsi_spec.rb b/spec/unit/provider/group/windows_adsi_spec.rb index 21b967509..d28601172 100644 --- a/spec/unit/provider/group/windows_adsi_spec.rb +++ b/spec/unit/provider/group/windows_adsi_spec.rb @@ -24,12 +24,64 @@ describe Puppet::Type.type(:group).provider(:windows_adsi) do names = ['group1', 'group2', 'group3'] stub_groups = names.map{|n| stub(:name => n)} - connection.stubs(:execquery).with("select name from win32_group").returns stub_groups + 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", :if => Puppet.features.microsoft_windows? do + + let(:user1) { stub(:account => 'user1', :domain => '.', :to_s => 'user1sid') } + let(:user2) { stub(:account => 'user2', :domain => '.', :to_s => 'user2sid') } + + before :each do + Puppet::Util::Windows::Security.stubs(:name_to_sid_object).with('user1').returns(user1) + Puppet::Util::Windows::Security.stubs(:name_to_sid_object).with('user2').returns(user2) + end + + describe "#members_insync?" do + it "should return false when current is nil" do + provider.members_insync?(nil, ['user2']).should be_false + end + it "should return false when should is nil" do + provider.members_insync?(['user1'], nil).should be_false + end + it "should return false for differing lists of members" do + provider.members_insync?(['user1'], ['user2']).should be_false + provider.members_insync?(['user1'], []).should be_false + provider.members_insync?([], ['user2']).should be_false + end + it "should return true for same lists of members" do + provider.members_insync?(['user1', 'user2'], ['user1', 'user2']).should be_true + end + it "should return true for same lists of unordered members" do + provider.members_insync?(['user1', 'user2'], ['user2', 'user1']).should be_true + 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 + 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 it "should be able to provide a list of members" do provider.group.stubs(:members).returns ['user1', 'user2', 'user3'] @@ -37,11 +89,22 @@ describe Puppet::Type.type(:group).provider(:windows_adsi) do provider.members.should =~ ['user1', 'user2', 'user3'] end - it "should be able to set group members" do + it "should be able to set group members", :if => Puppet.features.microsoft_windows? do provider.group.stubs(:members).returns ['user1', 'user2'] - provider.group.expects(:remove_members).with('user1') - provider.group.expects(:add_members).with('user3') + 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::Security.expects(:name_to_sid_object).with('user2').returns(member_sids[1]) + Puppet::Util::Windows::Security.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 @@ -97,4 +160,8 @@ describe Puppet::Type.type(:group).provider(:windows_adsi) 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", :if => Puppet.features.microsoft_windows? do + provider.members_to_s(['.\Administrators']).should == 'BUILTIN\Administrators' + end end diff --git a/spec/unit/provider/nameservice/directoryservice_spec.rb b/spec/unit/provider/nameservice/directoryservice_spec.rb index e7fdcbef5..fbb74d0f3 100755 --- a/spec/unit/provider/nameservice/directoryservice_spec.rb +++ b/spec/unit/provider/nameservice/directoryservice_spec.rb @@ -110,7 +110,7 @@ describe 'DirectoryService password behavior' do it 'should execute convert_binary_to_xml once when getting the password on >= 10.7' do subject.expects(:convert_binary_to_xml).returns({'SALTED-SHA512' => StringIO.new(pw_string)}) - File.expects(:exists?).with(plist_path).once.returns(true) + Puppet::FileSystem::File.expects(:exist?).with(plist_path).once.returns(true) Plist.expects(:parse_xml).returns(shadow_hash_data) # On Mac OS X 10.7 we first need to convert to xml when reading the password subject.expects(:plutil).with('-convert', 'xml1', '-o', '/dev/stdout', plist_path) @@ -126,7 +126,7 @@ describe 'DirectoryService password behavior' do it 'should convert xml-to-binary and binary-to-xml when setting the pw on >= 10.7' do subject.expects(:convert_binary_to_xml).returns({'SALTED-SHA512' => StringIO.new(pw_string)}) subject.expects(:convert_xml_to_binary).returns(binary_plist) - File.expects(:exists?).with(plist_path).once.returns(true) + Puppet::FileSystem::File.expects(:exist?).with(plist_path).once.returns(true) Plist.expects(:parse_xml).returns(shadow_hash_data) # On Mac OS X 10.7 we first need to convert to xml subject.expects(:plutil).with('-convert', 'xml1', '-o', '/dev/stdout', plist_path) @@ -138,7 +138,7 @@ describe 'DirectoryService password behavior' do it '[#13686] should handle an empty ShadowHashData field in the users plist' do subject.expects(:convert_xml_to_binary).returns(binary_plist) - File.expects(:exists?).with(plist_path).once.returns(true) + Puppet::FileSystem::File.expects(:exist?).with(plist_path).once.returns(true) Plist.expects(:parse_xml).returns({'ShadowHashData' => nil}) subject.expects(:plutil).with('-convert', 'xml1', '-o', '/dev/stdout', plist_path) subject.expects(:plutil).with('-convert', 'binary1', plist_path) diff --git a/spec/unit/provider/package/apt_spec.rb b/spec/unit/provider/package/apt_spec.rb index 5e75a569f..5d7b3e4e6 100755 --- a/spec/unit/provider/package/apt_spec.rb +++ b/spec/unit/provider/package/apt_spec.rb @@ -63,7 +63,7 @@ Version table: it "should preseed with the provided responsefile when preseeding is called for" do @resource.expects(:[]).with(:responsefile).returns "/my/file" - FileTest.expects(:exist?).with("/my/file").returns true + Puppet::FileSystem::File.expects(:exist?).with("/my/file").returns true @provider.expects(:info) @provider.expects(:preseed).with("/my/file") diff --git a/spec/unit/provider/package/aptrpm_spec.rb b/spec/unit/provider/package/aptrpm_spec.rb index 6c77bee3a..83d343242 100755 --- a/spec/unit/provider/package/aptrpm_spec.rb +++ b/spec/unit/provider/package/aptrpm_spec.rb @@ -19,7 +19,7 @@ describe Puppet::Type.type(:package).provider(:aptrpm) do def rpm pkg.provider.expects(:rpm). with('-q', 'faff', '--nosignature', '--nodigest', '--qf', - "'%{NAME} %|EPOCH?{%{EPOCH}}:{0}| %{VERSION} %{RELEASE} %{ARCH} :DESC: %{SUMMARY}\\n'") + "%{NAME} %|EPOCH?{%{EPOCH}}:{0}| %{VERSION} %{RELEASE} %{ARCH} :DESC: %{SUMMARY}\\n") end it "should report absent packages" do diff --git a/spec/unit/provider/package/msi_spec.rb b/spec/unit/provider/package/msi_spec.rb index 50ff40deb..192b0a749 100755 --- a/spec/unit/provider/package/msi_spec.rb +++ b/spec/unit/provider/package/msi_spec.rb @@ -24,9 +24,8 @@ describe Puppet::Type.type(:package).provider(:msi) do MsiPackage.stubs(:installer).returns(installer) end - before :each do - # make sure we never try to execute msiexec - provider.expects(:execute).never + def expect_execute(command, status) + provider.expects(:execute).with(command, execute_options).returns(Puppet::Util::Execution::ProcessOutput.new('',status)) end describe 'provider features' do @@ -38,6 +37,7 @@ describe Puppet::Type.type(:package).provider(:msi) do describe 'on Windows', :as_platform => :windows do it 'should not be the default provider' do + # provider.expects(:execute).never Puppet::Type.type(:package).defaultprovider.should_not == subject.class end end @@ -90,9 +90,7 @@ describe Puppet::Type.type(:package).provider(:msi) do end context '#install' do - before :each do - provider.stubs(:execute) - end + let (:command) { "msiexec.exe /qn /norestart /i #{source}" } it 'should require the source parameter' do resource = Puppet::Type.type(:package).new(:name => name, :provider => :msi) @@ -104,40 +102,27 @@ describe Puppet::Type.type(:package).provider(:msi) do it 'should install using the source and install_options' do resource[:install_options] = { 'INSTALLDIR' => 'C:\mysql-5.1' } - - provider.expects(:execute).with("msiexec.exe /qn /norestart /i #{source} INSTALLDIR=C:\\mysql-5.1", execute_options) - provider.expects(:exit_status).returns(0) - - provider.install - end - - it 'should warn if the package requests a reboot' do - provider.stubs(:exit_status).returns(194) - - provider.expects(:warning).with('The package requested a reboot to finish the operation.') + expect_execute("#{command} INSTALLDIR=C:\\mysql-5.1", 0) provider.install end it 'should warn if reboot initiated' do - provider.stubs(:exit_status).returns(1641) - + expect_execute(command, 1641) provider.expects(:warning).with('The package installed successfully and the system is rebooting now.') provider.install end it 'should warn if reboot required' do - provider.stubs(:exit_status).returns(3010) - + expect_execute(command, 3010) provider.expects(:warning).with('The package installed successfully, but the system must be rebooted.') provider.install end it 'should fail otherwise', :if => Puppet.features.microsoft_windows? do - provider.stubs(:execute) - provider.stubs(:exit_status).returns(5) + expect_execute(command, 5) expect do provider.install @@ -146,6 +131,9 @@ describe Puppet::Type.type(:package).provider(:msi) do end context '#uninstall' do + + let (:command) { "msiexec.exe /qn /norestart /x #{productcode}" } + before :each do resource[:ensure] = :absent provider.set(:productcode => productcode) @@ -159,42 +147,27 @@ describe Puppet::Type.type(:package).provider(:msi) do end it 'should uninstall using the productcode' do - provider.expects(:execute).with("msiexec.exe /qn /norestart /x #{productcode}", execute_options) - provider.expects(:exit_status).returns(0) - - provider.uninstall - end - - it 'should warn if the package requests a reboot' do - provider.stubs(:execute) - provider.stubs(:exit_status).returns(194) - - provider.expects(:warning).with('The package requested a reboot to finish the operation.') + expect_execute(command, 0) provider.uninstall end it 'should warn if reboot initiated' do - provider.stubs(:execute) - provider.stubs(:exit_status).returns(1641) - + expect_execute(command, 1641) provider.expects(:warning).with('The package uninstalled successfully and the system is rebooting now.') provider.uninstall end it 'should warn if reboot required' do - provider.stubs(:execute) - provider.stubs(:exit_status).returns(3010) - + expect_execute(command, 3010) provider.expects(:warning).with('The package uninstalled successfully, but the system must be rebooted.') provider.uninstall end it 'should fail otherwise', :if => Puppet.features.microsoft_windows? do - provider.stubs(:execute) - provider.stubs(:exit_status).returns(5) + expect_execute(command, 5) expect do provider.uninstall diff --git a/spec/unit/provider/package/openbsd_spec.rb b/spec/unit/provider/package/openbsd_spec.rb index 5a7ef7b46..48ec4bedb 100755 --- a/spec/unit/provider/package/openbsd_spec.rb +++ b/spec/unit/provider/package/openbsd_spec.rb @@ -10,7 +10,7 @@ describe provider_class do def expect_read_from_pkgconf(lines) pkgconf = stub(:readlines => lines) - File.expects(:exist?).with('/etc/pkg.conf').returns(true) + Puppet::FileSystem::File.expects(:exist?).with('/etc/pkg.conf').returns(true) File.expects(:open).with('/etc/pkg.conf', 'rb').returns(pkgconf) end @@ -76,7 +76,7 @@ describe provider_class do context "#install" do it "should fail if the resource doesn't have a source" do - File.expects(:exist?).with('/etc/pkg.conf').returns(false) + Puppet::FileSystem::File.expects(:exist?).with('/etc/pkg.conf').returns(false) expect { provider.install @@ -84,7 +84,7 @@ describe provider_class do end it "should fail if /etc/pkg.conf exists, but is not readable" do - File.expects(:exist?).with('/etc/pkg.conf').returns(true) + Puppet::FileSystem::File.expects(:exist?).with('/etc/pkg.conf').returns(true) File.expects(:open).with('/etc/pkg.conf', 'rb').raises(Errno::EACCES) expect { diff --git a/spec/unit/provider/package/rpm_spec.rb b/spec/unit/provider/package/rpm_spec.rb index e438b51a5..e81abafde 100755 --- a/spec/unit/provider/package/rpm_spec.rb +++ b/spec/unit/provider/package/rpm_spec.rb @@ -20,7 +20,8 @@ describe provider_class do let(:resource) do Puppet::Type.type(:package).new( :name => resource_name, - :ensure => :installed + :ensure => :installed, + :provider => 'rpm' ) end @@ -30,7 +31,7 @@ describe provider_class do provider end - let(:nevra_format) { %Q{'%{NAME} %|EPOCH?{%{EPOCH}}:{0}| %{VERSION} %{RELEASE} %{ARCH} :DESC: %{SUMMARY}\\n'} } + let(:nevra_format) { %Q{%{NAME} %|EPOCH?{%{EPOCH}}:{0}| %{VERSION} %{RELEASE} %{ARCH} :DESC: %{SUMMARY}\\n} } let(:execute_options) do {:failonfail => true, :combine => true, :custom_environment => {}} end @@ -47,7 +48,7 @@ describe provider_class do describe "self.instances" do describe "with a modern version of RPM" do it "should include all the modern flags" do - Puppet::Util::Execution.expects(:execpipe).with("/bin/rpm -qa --nosignature --nodigest --qf #{nevra_format}").yields(packages) + Puppet::Util::Execution.expects(:execpipe).with("/bin/rpm -qa --nosignature --nodigest --qf '#{nevra_format}'").yields(packages) installed_packages = subject.instances end @@ -56,7 +57,7 @@ describe provider_class do describe "with a version of RPM < 4.1" do let(:rpm_version) { "RPM version 4.0.2\n" } it "should exclude the --nosignature flag" do - Puppet::Util::Execution.expects(:execpipe).with("/bin/rpm -qa --nodigest --qf #{nevra_format}").yields(packages) + Puppet::Util::Execution.expects(:execpipe).with("/bin/rpm -qa --nodigest --qf '#{nevra_format}'").yields(packages) installed_packages = subject.instances end @@ -65,14 +66,14 @@ describe provider_class do describe "with a version of RPM < 4.0.2" do let(:rpm_version) { "RPM version 3.0.5\n" } it "should exclude the --nodigest flag" do - Puppet::Util::Execution.expects(:execpipe).with("/bin/rpm -qa --qf #{nevra_format}").yields(packages) + Puppet::Util::Execution.expects(:execpipe).with("/bin/rpm -qa --qf '#{nevra_format}'").yields(packages) installed_packages = subject.instances end end it "returns an array of packages" do - Puppet::Util::Execution.expects(:execpipe).with("/bin/rpm -qa --nosignature --nodigest --qf #{nevra_format}").yields(packages) + Puppet::Util::Execution.expects(:execpipe).with("/bin/rpm -qa --nosignature --nodigest --qf '#{nevra_format}'").yields(packages) installed_packages = subject.instances @@ -138,23 +139,40 @@ describe provider_class do describe "when not already installed" do it "should only include the '-i' flag" do - Puppet::Util::Execution.expects(:execute).with(["/bin/rpm", "-i", '/path/to/package'], execute_options) + Puppet::Util::Execution.expects(:execute).with(["/bin/rpm", ["-i"], '/path/to/package'], execute_options) provider.install end - end + end - describe "when an older version is installed" do - before(:each) do - # Force the provider to think a version of the package is already installed - # This is real hacky. I'm sorry. --jeffweiss 25 Jan 2013 - provider.instance_variable_get('@property_hash')[:ensure] = '1.2.3.3' - end + describe "when installed with options" do + let(:resource) do + Puppet::Type.type(:package).new( + :name => resource_name, + :ensure => :installed, + :provider => 'rpm', + :source => '/path/to/package', + :install_options => ['-D', {'--test' => 'value'}, '-Q'] + ) + end - it "should include the '-U --oldpackage' flags" do - Puppet::Util::Execution.expects(:execute).with(["/bin/rpm", ["-U", "--oldpackage"], '/path/to/package'], execute_options) + it "should include the options" do + Puppet::Util::Execution.expects(:execute).with(["/bin/rpm", ["-i", "-D", "--test=value", "-Q"], '/path/to/package'], execute_options) provider.install - end - end + end + end + + describe "when an older version is installed" do + before(:each) do + # Force the provider to think a version of the package is already installed + # This is real hacky. I'm sorry. --jeffweiss 25 Jan 2013 + provider.instance_variable_get('@property_hash')[:ensure] = '1.2.3.3' + end + + it "should include the '-U --oldpackage' flags" do + Puppet::Util::Execution.expects(:execute).with(["/bin/rpm", ["-U", "--oldpackage"], '/path/to/package'], execute_options) + provider.install + end + end end describe "#latest" do @@ -268,6 +286,31 @@ describe provider_class do end end + describe "#install_options" do + it "should return empty array by default" do + provider.install_options.should == [] + end + + it "should return install_options when set" do + provider.resource[:install_options] = ['-n'] + provider.install_options.should == ['-n'] + end + + it "should return multiple install_options when set" do + provider.resource[:install_options] = ['-L', '/opt/puppet'] + provider.install_options.should == ['-L', '/opt/puppet'] + end + + it 'should return install_options when set as hash' do + provider.resource[:install_options] = { '-Darch' => 'vax' } + provider.install_options.should == ['-Darch=vax'] + end + it 'should return install_options when an array with hashes' do + provider.resource[:install_options] = [ '-L', { '-Darch' => 'vax' }] + provider.install_options.should == ['-L', '-Darch=vax'] + end + end + describe ".nodigest" do { '4.0' => nil, '4.0.1' => nil, diff --git a/spec/unit/provider/package/windows_spec.rb b/spec/unit/provider/package/windows_spec.rb index d07d584de..4541f9ea7 100755 --- a/spec/unit/provider/package/windows_spec.rb +++ b/spec/unit/provider/package/windows_spec.rb @@ -14,8 +14,7 @@ describe Puppet::Type.type(:package).provider(:windows) do end def expect_execute(command, status) - provider.expects(:execute).with(command, execute_options) - provider.expects(:exit_status).returns(status) + provider.expects(:execute).with(command, execute_options).returns(Puppet::Util::Execution::ProcessOutput.new('',status)) end describe 'provider features' do @@ -23,6 +22,7 @@ describe Puppet::Type.type(:package).provider(:windows) do it { should be_uninstallable } it { should be_install_options } it { should be_uninstall_options } + it { should be_versionable } end describe 'on Windows', :if => Puppet.features.microsoft_windows? do @@ -36,17 +36,19 @@ describe Puppet::Type.type(:package).provider(:windows) do pkg1 = stub('pkg1') pkg2 = stub('pkg2') - prov1 = stub('prov1', :name => 'pkg1', :package => pkg1) - prov2 = stub('prov2', :name => 'pkg2', :package => pkg2) + prov1 = stub('prov1', :name => 'pkg1', :version => '1.0.0', :package => pkg1) + prov2 = stub('prov2', :name => 'pkg2', :version => nil, :package => pkg2) Puppet::Provider::Package::Windows::Package.expects(:map).multiple_yields([prov1], [prov2]).returns([prov1, prov2]) providers = provider.class.instances providers.count.should == 2 providers[0].name.should == 'pkg1' + providers[0].version.should == '1.0.0' providers[0].package.should == pkg1 providers[1].name.should == 'pkg2' + providers[1].version.should be_nil providers[1].package.should == pkg2 end @@ -59,13 +61,21 @@ describe Puppet::Type.type(:package).provider(:windows) do context '#query' do it 'should return the hash of the matched packaged' do - pkg = mock(:name => 'pkg1') + pkg = mock(:name => 'pkg1', :version => nil) pkg.expects(:match?).returns(true) Puppet::Provider::Package::Windows::Package.expects(:find).yields(pkg) provider.query.should == { :name => 'pkg1', :ensure => :installed, :provider => :windows } end + it 'should include the version string when present' do + pkg = mock(:name => 'pkg1', :version => '1.0.0') + pkg.expects(:match?).returns(true) + Puppet::Provider::Package::Windows::Package.expects(:find).yields(pkg) + + provider.query.should == { :name => 'pkg1', :ensure => '1.0.0', :provider => :windows } + end + it 'should return nil if no package was found' do Puppet::Provider::Package::Windows::Package.expects(:find) @@ -102,13 +112,6 @@ describe Puppet::Type.type(:package).provider(:windows) do provider.install end - it 'should warn if the package requests a reboot' do - expect_execute(command, 194) - provider.expects(:warning).with('The package requested a reboot to finish the operation.') - - provider.install - end - it 'should warn if reboot initiated' do expect_execute(command, 1641) provider.expects(:warning).with('The package installed successfully and the system is rebooting now.') @@ -161,13 +164,6 @@ describe Puppet::Type.type(:package).provider(:windows) do provider.uninstall end - it 'should warn if the package requests a reboot' do - expect_execute(command, 194) - provider.expects(:warning).with('The package requested a reboot to finish the operation.') - - provider.uninstall - end - it 'should warn if reboot initiated' do expect_execute(command, 1641) provider.expects(:warning).with('The package uninstalled successfully and the system is rebooting now.') diff --git a/spec/unit/provider/package/yum_spec.rb b/spec/unit/provider/package/yum_spec.rb index 7a019bea7..c5d89ae60 100755 --- a/spec/unit/provider/package/yum_spec.rb +++ b/spec/unit/provider/package/yum_spec.rb @@ -153,7 +153,7 @@ _pkg mysummaryless 0 1.2.3.4 5.el4 noarch end def expect_execpipe_to_provide_package_info_for_an_rpm_query - Puppet::Util::Execution.expects(:execpipe).with("/bin/rpm -qa --nosignature --nodigest --qf #{nevra_format}").yields(packages) + Puppet::Util::Execution.expects(:execpipe).with("/bin/rpm -qa --nosignature --nodigest --qf '#{nevra_format}'").yields(packages) end def expect_python_yumhelper_call_to_return_latest_info diff --git a/spec/unit/provider/service/base_spec.rb b/spec/unit/provider/service/base_spec.rb index b78bb0134..9d6a7fdcc 100755 --- a/spec/unit/provider/service/base_spec.rb +++ b/spec/unit/provider/service/base_spec.rb @@ -34,7 +34,7 @@ describe "base service provider" do end before :each do - File.unlink(flag) if File.exist?(flag) + Puppet::FileSystem::File.unlink(flag) if Puppet::FileSystem::File.exist?(flag) end it { should be } diff --git a/spec/unit/provider/service/daemontools_spec.rb b/spec/unit/provider/service/daemontools_spec.rb index c538a0ced..4c35e0586 100755 --- a/spec/unit/provider/service/daemontools_spec.rb +++ b/spec/unit/provider/service/daemontools_spec.rb @@ -99,9 +99,14 @@ describe provider_class do end describe "when enabling" do - it "should create a symlink between daemon dir and service dir" do - FileTest.stubs(:symlink?).returns(false) - File.expects(:symlink).with(File.join(@daemondir,"myservice"), File.join(@servicedir,"myservice")).returns(0) + it "should create a symlink between daemon dir and service dir", :if => Puppet.features.manages_symlinks? do + daemon_path = File.join(@daemondir, "myservice") + stub_daemon = stub(daemon_path, :symlink? => false) + Puppet::FileSystem::File.expects(:new).with(daemon_path).returns(stub_daemon) + service_path = File.join(@servicedir, "myservice") + mock_service = mock(service_path, :symlink? => false) + Puppet::FileSystem::File.expects(:new).with(service_path).returns(mock_service) + stub_daemon.expects(:symlink).returns(0) @provider.enable end end @@ -109,16 +114,19 @@ describe provider_class do describe "when disabling" do it "should remove the symlink between daemon dir and service dir" do FileTest.stubs(:directory?).returns(false) - FileTest.stubs(:symlink?).returns(true) - File.expects(:unlink).with(File.join(@servicedir,"myservice")) + path = File.join(@servicedir,"myservice") + mocked_file = mock(path, :symlink? => true) + Puppet::FileSystem::File.expects(:new).with(path).returns(mocked_file) + Puppet::FileSystem::File.expects(:unlink).with(path) @provider.stubs(:texecute).returns("") @provider.disable end it "should stop the service" do FileTest.stubs(:directory?).returns(false) - FileTest.stubs(:symlink?).returns(true) - File.stubs(:unlink) + mocked_file = mock('anything', :symlink? => true) + Puppet::FileSystem::File.expects(:new).returns(mocked_file) + Puppet::FileSystem::File.stubs(:unlink) @provider.expects(:stop) @provider.disable end @@ -134,7 +142,9 @@ describe provider_class do [true, false].each do |t| it "should return #{t} if the symlink exists" do @provider.stubs(:status).returns(:stopped) - FileTest.stubs(:symlink?).returns(t) + path = File.join(@servicedir,"myservice") + mocked_file = mock(path, :symlink? => t) + Puppet::FileSystem::File.expects(:new).with(path).returns(mocked_file) @provider.enabled?.should == "#{t}".to_sym end diff --git a/spec/unit/provider/service/freebsd_spec.rb b/spec/unit/provider/service/freebsd_spec.rb index 81e3fc1af..69b923f9a 100755 --- a/spec/unit/provider/service/freebsd_spec.rb +++ b/spec/unit/provider/service/freebsd_spec.rb @@ -62,13 +62,13 @@ OUTPUT end it "should enable only the selected service" do - File.stubs(:exists?).with('/etc/rc.conf').returns(true) + Puppet::FileSystem::File.stubs(:exist?).with('/etc/rc.conf').returns(true) File.stubs(:read).with('/etc/rc.conf').returns("openntpd_enable=\"NO\"\nntpd_enable=\"NO\"\n") fh = stub 'fh' File.stubs(:open).with('/etc/rc.conf', File::WRONLY).yields(fh) fh.expects(:<<).with("openntpd_enable=\"NO\"\nntpd_enable=\"YES\"\n") - File.stubs(:exists?).with('/etc/rc.conf.local').returns(false) - File.stubs(:exists?).with('/etc/rc.conf.d/ntpd').returns(false) + Puppet::FileSystem::File.stubs(:exist?).with('/etc/rc.conf.local').returns(false) + Puppet::FileSystem::File.stubs(:exist?).with('/etc/rc.conf.d/ntpd').returns(false) @provider.rc_replace('ntpd', 'ntpd', 'YES') end diff --git a/spec/unit/provider/service/gentoo_spec.rb b/spec/unit/provider/service/gentoo_spec.rb index dab315537..aa802430e 100755 --- a/spec/unit/provider/service/gentoo_spec.rb +++ b/spec/unit/provider/service/gentoo_spec.rb @@ -13,7 +13,9 @@ describe Puppet::Type.type(:service).provider(:gentoo) do # The initprovider (parent of the gentoo provider) does a stat call # before it even tries to execute an initscript. We use sshd in all the # tests so make sure it is considered present. - File.stubs(:stat).with('/etc/init.d/sshd') + sshd_path = '/etc/init.d/sshd' + stub_file = stub(sshd_path, :stat => stub('stat')) + Puppet::FileSystem::File.stubs(:new).with(sshd_path).returns stub_file end let :initscripts do @@ -48,7 +50,6 @@ describe Puppet::Type.type(:service).provider(:gentoo) do it "should get a list of services from /etc/init.d but exclude helper scripts" do FileTest.expects(:directory?).with('/etc/init.d').returns true - File.stubs(:symlink?).returns(false) Dir.expects(:entries).with('/etc/init.d').returns initscripts (initscripts - helperscripts).each do |script| FileTest.expects(:executable?).with("/etc/init.d/#{script}").returns true @@ -56,6 +57,8 @@ describe Puppet::Type.type(:service).provider(:gentoo) do helperscripts.each do |script| FileTest.expects(:executable?).with("/etc/init.d/#{script}").never end + + Puppet::FileSystem::File.stubs(:new).returns stub('file', :symlink? => false) described_class.instances.map(&:name).should == [ 'alsasound', 'bootmisc', diff --git a/spec/unit/provider/service/init_spec.rb b/spec/unit/provider/service/init_spec.rb index e88672b40..896f13ac1 100755 --- a/spec/unit/provider/service/init_spec.rb +++ b/spec/unit/provider/service/init_spec.rb @@ -62,12 +62,12 @@ describe Puppet::Type.type(:service).provider(:init) do described_class.instances.should be_all { |provider| provider.get(:hasstatus) == true } end - it "should discard upstart jobs" do + it "should discard upstart jobs", :if => Puppet.features.manages_symlinks? do not_init_service, *valid_services = @services - File.stubs(:symlink?).returns false - File.stubs(:symlink?).with("tmp/#{not_init_service}").returns(true) - File.stubs(:readlink).with("tmp/#{not_init_service}").returns("/lib/init/upstart-job") - + path = "tmp/#{not_init_service}" + mocked_file = mock(path, :symlink? => true, :readlink => "/lib/init/upstart-job") + Puppet::FileSystem::File.stubs(:new).returns stub('file', :symlink? => false) + Puppet::FileSystem::File.expects(:new).with(path).returns(mocked_file) described_class.instances.map(&:name).should == valid_services end @@ -82,7 +82,7 @@ describe Puppet::Type.type(:service).provider(:init) do describe "when checking valid paths" do it "should discard paths that do not exist" do File.expects(:directory?).with(paths[0]).returns false - File.expects(:exist?).with(paths[0]).returns false + Puppet::FileSystem::File.expects(:exist?).with(paths[0]).returns false File.expects(:directory?).with(paths[1]).returns true provider.paths.should == [paths[1]] @@ -90,7 +90,7 @@ describe Puppet::Type.type(:service).provider(:init) do it "should discard paths that are not directories" do paths.each do |path| - File.expects(:exist?).with(path).returns true + Puppet::FileSystem::File.expects(:exist?).with(path).returns true File.expects(:directory?).with(path).returns false end provider.paths.should be_empty @@ -103,28 +103,28 @@ describe Puppet::Type.type(:service).provider(:init) do end it "should be able to find the init script in the service path" do - File.expects(:exist?).with("#{paths[0]}/myservice").returns true - File.expects(:exist?).with("#{paths[1]}/myservice").never # first one wins + Puppet::FileSystem::File.expects(:exist?).with("#{paths[0]}/myservice").returns true + Puppet::FileSystem::File.expects(:exist?).with("#{paths[1]}/myservice").never # first one wins provider.initscript.should == "/service/path/myservice" end it "should be able to find the init script in an alternate service path" do - File.expects(:exist?).with("#{paths[0]}/myservice").returns false - File.expects(:exist?).with("#{paths[1]}/myservice").returns true + Puppet::FileSystem::File.expects(:exist?).with("#{paths[0]}/myservice").returns false + Puppet::FileSystem::File.expects(:exist?).with("#{paths[1]}/myservice").returns true provider.initscript.should == "/alt/service/path/myservice" end it "should be able to find the init script if it ends with .sh" do - File.expects(:exist?).with("#{paths[0]}/myservice").returns false - File.expects(:exist?).with("#{paths[1]}/myservice").returns false - File.expects(:exist?).with("#{paths[0]}/myservice.sh").returns true + Puppet::FileSystem::File.expects(:exist?).with("#{paths[0]}/myservice").returns false + Puppet::FileSystem::File.expects(:exist?).with("#{paths[1]}/myservice").returns false + Puppet::FileSystem::File.expects(:exist?).with("#{paths[0]}/myservice.sh").returns true provider.initscript.should == "/service/path/myservice.sh" end it "should fail if the service isn't there" do paths.each do |path| - File.expects(:exist?).with("#{path}/myservice").returns false - File.expects(:exist?).with("#{path}/myservice.sh").returns false + Puppet::FileSystem::File.expects(:exist?).with("#{path}/myservice").returns false + Puppet::FileSystem::File.expects(:exist?).with("#{path}/myservice.sh").returns false end expect { provider.initscript }.to raise_error(Puppet::Error, "Could not find init script for 'myservice'") end @@ -134,7 +134,7 @@ describe Puppet::Type.type(:service).provider(:init) do before :each do File.stubs(:directory?).with("/service/path").returns true File.stubs(:directory?).with("/alt/service/path").returns true - File.stubs(:exist?).with("/service/path/myservice").returns true + Puppet::FileSystem::File.stubs(:exist?).with("/service/path/myservice").returns true end [:start, :stop, :status, :restart].each do |method| diff --git a/spec/unit/provider/service/launchd_spec.rb b/spec/unit/provider/service/launchd_spec.rb index fc82fdd00..b126fb22d 100755 --- a/spec/unit/provider/service/launchd_spec.rb +++ b/spec/unit/provider/service/launchd_spec.rb @@ -209,29 +209,82 @@ describe Puppet::Type.type(:service).provider(:launchd) do end end - describe "when encountering malformed plists" do - let(:plist_without_label) do - { - 'LimitLoadToSessionType' => 'Aqua' - } - end - let(:busted_plist_path) { '/Library/LaunchAgents/org.busted.plist' } - - it "[17624] should warn that the plist in question is being skipped" do - provider.expects(:launchd_paths).returns(['/Library/LaunchAgents']) - provider.expects(:return_globbed_list_of_file_paths).with('/Library/LaunchAgents').returns([busted_plist_path]) - provider.expects(:read_plist).with(busted_plist_path).returns(plist_without_label) - Puppet.expects(:warning).with("The #{busted_plist_path} plist does not contain a 'label' key; Puppet is skipping it") - provider.jobsearch - end - - it "[15929] should skip plists that plutil cannot read" do - provider.expects(:plutil).with('-convert', 'xml1', '-o', '/dev/stdout', - busted_plist_path).raises(Puppet::ExecutionFailure, 'boom') - Puppet.expects(:warning).with("Cannot read file #{busted_plist_path}; " + - "Puppet is skipping it. \n" + - "Details: boom") - provider.read_plist(busted_plist_path) + describe "make_label_to_path_map" do + before do + # clear out this class variable between runs + if provider.instance_variable_defined? :@label_to_path_map + provider.send(:remove_instance_variable, :@label_to_path_map) + end + end + describe "when encountering malformed plists" do + let(:plist_without_label) do + { + 'LimitLoadToSessionType' => 'Aqua' + } + end + let(:busted_plist_path) { '/Library/LaunchAgents/org.busted.plist' } + + it "[17624] should warn that the plist in question is being skipped" do + provider.expects(:launchd_paths).returns(['/Library/LaunchAgents']) + provider.expects(:return_globbed_list_of_file_paths).with('/Library/LaunchAgents').returns([busted_plist_path]) + provider.expects(:read_plist).with(busted_plist_path).returns(plist_without_label) + Puppet.expects(:warning).with("The #{busted_plist_path} plist does not contain a 'label' key; Puppet is skipping it") + provider.make_label_to_path_map + end + + it "[15929] should skip plists that plutil cannot read" do + provider.expects(:plutil).with('-convert', 'xml1', '-o', '/dev/stdout', + busted_plist_path).raises(Puppet::ExecutionFailure, 'boom') + Puppet.expects(:warning).with("Cannot read file #{busted_plist_path}; " + + "Puppet is skipping it. \n" + + "Details: boom") + provider.read_plist(busted_plist_path) + end + end + it "should return the cached value when available" do + provider.instance_variable_set(:@label_to_path_map, {'xx'=>'yy'}) + provider.make_label_to_path_map.should eq({'xx'=>'yy'}) + end + describe "when successful" do + let(:launchd_dir) { '/Library/LaunchAgents' } + let(:plist) { launchd_dir + '/foo.bar.service.plist' } + let(:label) { 'foo.bar.service' } + before do + provider.instance_variable_set(:@label_to_path_map, nil) + provider.expects(:launchd_paths).returns([launchd_dir]) + provider.expects(:return_globbed_list_of_file_paths).with(launchd_dir).returns([plist]) + provider.expects(:read_plist).with(plist).returns({'Label'=>'foo.bar.service'}) + end + it "should read the plists and return their contents" do + provider.make_label_to_path_map.should eq({label=>plist}) + end + it "should re-read the plists and return their contents when refreshed" do + provider.instance_variable_set(:@label_to_path_map, {'xx'=>'yy'}) + provider.make_label_to_path_map(true).should eq({label=>plist}) + end + end + end + + describe "jobsearch" do + let(:map) { {"org.mozilla.puppet" => "/path/to/puppet.plist", + "org.mozilla.python" => "/path/to/python.plist"} } + it "returns the entire map with no args" do + provider.expects(:make_label_to_path_map).returns(map) + provider.jobsearch.should == map + end + it "returns a singleton hash when given a label" do + provider.expects(:make_label_to_path_map).returns(map) + provider.jobsearch("org.mozilla.puppet").should == { "org.mozilla.puppet" => "/path/to/puppet.plist" } + end + it "refreshes the label_to_path_map when label is not found" do + provider.expects(:make_label_to_path_map).with().returns({}) + provider.expects(:make_label_to_path_map).with(true).returns(map) + provider.jobsearch("org.mozilla.puppet").should == { "org.mozilla.puppet" => "/path/to/puppet.plist" } + end + it "raises Puppet::Error when the label is still not found" do + provider.expects(:make_label_to_path_map).with().returns(map) + provider.expects(:make_label_to_path_map).with(true).returns(map) + expect { provider.jobsearch("NOSUCH") }.to raise_error(Puppet::Error) end end end diff --git a/spec/unit/provider/service/openbsd_spec.rb b/spec/unit/provider/service/openbsd_spec.rb new file mode 100644 index 000000000..8c417986d --- /dev/null +++ b/spec/unit/provider/service/openbsd_spec.rb @@ -0,0 +1,125 @@ +#!/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 do + before :each do + Puppet::Type.type(:service).stubs(:defaultprovider).returns described_class + Facter.stubs(:value).with(:operatingsystem).returns :openbsd + end + + let :rcscripts do + [ + 'apmd', + 'aucat', + 'cron', + 'puppetd' + ] + 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 + FileTest.expects(:directory?).with('/etc/rc.d').returns true + Dir.expects(:entries).with('/etc/rc.d').returns rcscripts + + rcscripts.each do |script| + FileTest.expects(:executable?).with("/etc/rc.d/#{script}").returns true + end + + described_class.instances.map(&:name).should == [ + 'apmd', + 'aucat', + 'cron', + 'puppetd' + ] + 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 => true) + provider.start + end + + it "should start the service otherwise" do + provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd')) + provider.expects(:execute).with(['/etc/rc.d/sshd', '-f', :start], :failonfail => true, :override_locale => false, :squelch => true) + provider.expects(:search).with('sshd').returns('/etc/rc.d/sshd') + 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 => true) + provider.stop + end + + it "should stop the service otherwise" do + provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd')) + provider.expects(:execute).with(['/etc/rc.d/sshd', :stop], :failonfail => true, :override_locale => false, :squelch => true) + provider.expects(:search).with('sshd').returns('/etc/rc.d/sshd') + 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(['/etc/rc.d/sshd', :status], :failonfail => false, :override_locale => false, :squelch => true).never + provider.expects(:execute).with(['/bin/foo'], :failonfail => false, :override_locale => false, :squelch => true) + provider.status + end + + it "should return :stopped when status command returns with a non-zero exitcode" do + provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :status => '/bin/foo')) + provider.expects(:execute).with(['/etc/rc.d/sshd', :status], :failonfail => false, :override_locale => false, :squelch => true).never + provider.expects(:execute).with(['/bin/foo'], :failonfail => false, :override_locale => false, :squelch => true) + $CHILD_STATUS.stubs(:exitstatus).returns 3 + provider.status.should == :stopped + end + + it "should return :running when status command returns with a zero exitcode" do + provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :status => '/bin/foo')) + provider.expects(:execute).with(['/etc/rc.d/sshd', :status], :failonfail => false, :override_locale => false, :squelch => true).never + provider.expects(:execute).with(['/bin/foo'], :failonfail => false, :override_locale => false, :squelch => 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(['/etc/rc.d/sshd', '-f', :restart], :failonfail => true, :override_locale => false, :squelch => true).never + provider.expects(:execute).with(['/bin/foo'], :failonfail => true, :override_locale => false, :squelch => true) + provider.restart + end + + it "should restart the service with rc-service restart if hasrestart is true" do + provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :hasrestart => true)) + provider.expects(:execute).with(['/etc/rc.d/sshd', '-f', :restart], :failonfail => true, :override_locale => false, :squelch => true) + provider.expects(:search).with('sshd').returns('/etc/rc.d/sshd') + provider.restart + end + + it "should restart the service with rc-service stop/start if hasrestart is false" do + provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :hasrestart => false)) + provider.expects(:execute).with(['/etc/rc.d/sshd', '-f', :restart], :failonfail => true, :override_locale => false, :squelch => true).never + provider.expects(:execute).with(['/etc/rc.d/sshd', :stop], :failonfail => true, :override_locale => false, :squelch => true) + provider.expects(:execute).with(['/etc/rc.d/sshd', '-f', :start], :failonfail => true, :override_locale => false, :squelch => true) + provider.expects(:search).with('sshd').returns('/etc/rc.d/sshd') + provider.restart + end + end +end diff --git a/spec/unit/provider/service/openwrt_spec.rb b/spec/unit/provider/service/openwrt_spec.rb index bc8a6323d..2113aceb4 100755 --- a/spec/unit/provider/service/openwrt_spec.rb +++ b/spec/unit/provider/service/openwrt_spec.rb @@ -33,7 +33,7 @@ describe Puppet::Type.type(:service).provider(:openwrt), :as_platform => :posix # All OpenWrt tests operate on the init script directly. It must exist. File.stubs(:directory?).with('/etc/init.d').returns true - File.stubs(:exist?).with('/etc/init.d/myservice').returns true + Puppet::FileSystem::File.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 diff --git a/spec/unit/provider/service/runit_spec.rb b/spec/unit/provider/service/runit_spec.rb index 5c5b37b37..cbdc19ba6 100755 --- a/spec/unit/provider/service/runit_spec.rb +++ b/spec/unit/provider/service/runit_spec.rb @@ -96,18 +96,25 @@ describe provider_class do end describe "when enabling" do - it "should create a symlink between daemon dir and service dir" do - FileTest.stubs(:symlink?).returns(false) - File.expects(:symlink).with(File.join(@daemondir,"myservice"), File.join(@servicedir,"myservice")).returns(0) + it "should create a symlink between daemon dir and service dir", :if => Puppet.features.manages_symlinks? do + daemon_path = File.join(@daemondir,"myservice") + mock_daemon = mock(daemon_path) + Puppet::FileSystem::File.expects(:new).with(daemon_path).returns(mock_daemon) + service_path = File.join(@servicedir,"myservice") + mock_service = mock(service_path, :symlink? => false) + Puppet::FileSystem::File.expects(:new).with(service_path).returns(mock_service) + mock_daemon.expects(:symlink).with(File.join(@servicedir,"myservice")).returns(0) @provider.enable end end describe "when disabling" do it "should remove the '/etc/service/myservice' symlink" do + path = File.join(@servicedir,"myservice") + mocked_file = mock(path, :symlink? => true) FileTest.stubs(:directory?).returns(false) - FileTest.stubs(:symlink?).returns(true) - File.expects(:unlink).with(File.join(@servicedir,"myservice")).returns(0) + Puppet::FileSystem::File.expects(:new).with(path).returns(mocked_file) + Puppet::FileSystem::File.expects(:unlink).with(path).returns(0) @provider.disable end end diff --git a/spec/unit/provider/service/upstart_spec.rb b/spec/unit/provider/service/upstart_spec.rb index e24857901..ed93386b3 100755 --- a/spec/unit/provider/service/upstart_spec.rb +++ b/spec/unit/provider/service/upstart_spec.rb @@ -51,8 +51,8 @@ describe Puppet::Type.type(:service).provider(:upstart) do describe "#search" do it "searches through paths to find a matching conf file" do File.stubs(:directory?).returns(true) - File.stubs(:exists?).returns(false) - File.expects(:exists?).with("/etc/init/foo-bar.conf").returns(true) + Puppet::FileSystem::File.stubs(:exist?).returns(false) + Puppet::FileSystem::File.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) @@ -61,8 +61,8 @@ describe Puppet::Type.type(:service).provider(:upstart) do it "searches for just the name of a compound named service" do File.stubs(:directory?).returns(true) - File.stubs(:exists?).returns(false) - File.expects(:exists?).with("/etc/init/network-interface.conf").returns(true) + Puppet::FileSystem::File.stubs(:exist?).returns(false) + Puppet::FileSystem::File.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) diff --git a/spec/unit/provider/ssh_authorized_key/parsed_spec.rb b/spec/unit/provider/ssh_authorized_key/parsed_spec.rb index 5cfa8994b..4d17ffe51 100755 --- a/spec/unit/provider/ssh_authorized_key/parsed_spec.rb +++ b/spec/unit/provider/ssh_authorized_key/parsed_spec.rb @@ -162,7 +162,7 @@ describe provider_class, :unless => Puppet.features.microsoft_windows? do end it "should create the directory" do - File.stubs(:exist?).with("/tmp/.ssh_dir").returns false + Puppet::FileSystem::File.stubs(:exist?).with("/tmp/.ssh_dir").returns false Dir.expects(:mkdir).with("/tmp/.ssh_dir", 0700) @provider.flush end @@ -199,19 +199,19 @@ describe provider_class, :unless => Puppet.features.microsoft_windows? do end it "should create the directory if it doesn't exist" do - File.stubs(:exist?).with(@dir).returns false + Puppet::FileSystem::File.stubs(:exist?).with(@dir).returns false Dir.expects(:mkdir).with(@dir,0700) @provider.flush end it "should not create or chown the directory if it already exist" do - File.stubs(:exist?).with(@dir).returns false + Puppet::FileSystem::File.stubs(:exist?).with(@dir).returns false Dir.expects(:mkdir).never @provider.flush end it "should absolutely not chown the directory to the user if it creates it" do - File.stubs(:exist?).with(@dir).returns false + Puppet::FileSystem::File.stubs(:exist?).with(@dir).returns false Dir.stubs(:mkdir).with(@dir,0700) uid = Puppet::Util.uid("nobody") File.expects(:chown).never @@ -219,7 +219,7 @@ describe provider_class, :unless => Puppet.features.microsoft_windows? do end it "should not create or chown the directory if it already exist" do - File.stubs(:exist?).with(@dir).returns false + Puppet::FileSystem::File.stubs(:exist?).with(@dir).returns false Dir.expects(:mkdir).never File.expects(:chown).never @provider.flush diff --git a/spec/unit/provider/user/directoryservice_spec.rb b/spec/unit/provider/user/directoryservice_spec.rb index e464e2c6f..fe72bfc4f 100755 --- a/spec/unit/provider/user/directoryservice_spec.rb +++ b/spec/unit/provider/user/directoryservice_spec.rb @@ -704,7 +704,7 @@ describe Puppet::Type.type(:user).provider(:directoryservice) do let(:stub_password_file) { stub('connection') } it 'should return a sha1 hash read from disk' do - File.expects(:exists?).with(password_hash_file).returns(true) + Puppet::FileSystem::File.expects(:exist?).with(password_hash_file).returns(true) File.expects(:file?).with(password_hash_file).returns(true) File.expects(:readable?).with(password_hash_file).returns(true) File.expects(:new).with(password_hash_file).returns(stub_password_file) @@ -714,18 +714,18 @@ describe Puppet::Type.type(:user).provider(:directoryservice) do end it 'should return nil if the password_hash_file does not exist' do - File.expects(:exists?).with(password_hash_file).returns(false) + Puppet::FileSystem::File.expects(:exist?).with(password_hash_file).returns(false) provider.class.get_sha1('user_guid').should == nil end it 'should return nil if the password_hash_file is not a file' do - File.expects(:exists?).with(password_hash_file).returns(true) + Puppet::FileSystem::File.expects(:exist?).with(password_hash_file).returns(true) File.expects(:file?).with(password_hash_file).returns(false) provider.class.get_sha1('user_guid').should == nil end it 'should raise an error if the password_hash_file is not readable' do - File.expects(:exists?).with(password_hash_file).returns(true) + Puppet::FileSystem::File.expects(:exist?).with(password_hash_file).returns(true) File.expects(:file?).with(password_hash_file).returns(true) File.expects(:readable?).with(password_hash_file).returns(false) expect { provider.class.get_sha1('user_guid').should == nil }.to raise_error Puppet::Error, /Could not read password hash file at #{password_hash_file}/ diff --git a/spec/unit/provider/user/windows_adsi_spec.rb b/spec/unit/provider/user/windows_adsi_spec.rb index 4e3a6f463..c25ccaf95 100755 --- a/spec/unit/provider/user/windows_adsi_spec.rb +++ b/spec/unit/provider/user/windows_adsi_spec.rb @@ -24,7 +24,7 @@ describe Puppet::Type.type(:user).provider(:windows_adsi) do it "should enumerate all users" do names = ['user1', 'user2', 'user3'] stub_users = names.map{|n| stub(:name => n)} - connection.stubs(:execquery).with("select name from win32_useraccount").returns(stub_users) + connection.stubs(:execquery).with('select name from win32_useraccount where localaccount = "TRUE"').returns(stub_users) described_class.instances.map(&:name).should =~ names end diff --git a/spec/unit/provider/zone/solaris_spec.rb b/spec/unit/provider/zone/solaris_spec.rb index 6fd58a46a..c143cabd1 100755 --- a/spec/unit/provider/zone/solaris_spec.rb +++ b/spec/unit/provider/zone/solaris_spec.rb @@ -138,7 +138,7 @@ net: it "should not require path if sysidcfg is specified" do resource[:path] = '/mypath' resource[:sysidcfg] = 'dummy' - File.stubs(:exists?).with('/mypath/root/etc/sysidcfg').returns true + Puppet::FileSystem::File.stubs(:exist?).with('/mypath/root/etc/sysidcfg').returns true File.stubs(:directory?).with('/mypath/root/etc').returns true provider.expects(:zoneadm).with(:boot) provider.start diff --git a/spec/unit/provider_spec.rb b/spec/unit/provider_spec.rb index 208094b7e..31e69f799 100755 --- a/spec/unit/provider_spec.rb +++ b/spec/unit/provider_spec.rb @@ -349,10 +349,10 @@ describe Puppet::Provider do command = Puppet::Util.which('sh') || Puppet::Util.which('cmd.exe') parent.commands :sh => command - FileTest.should be_exists parent.command(:sh) + Puppet::FileSystem::File.exist?(parent.command(:sh)).should be_true parent.command(:sh).should =~ /#{Regexp.escape(command)}$/ - FileTest.should be_exists child.command(:sh) + Puppet::FileSystem::File.exist?(child.command(:sh)).should be_true child.command(:sh).should =~ /#{Regexp.escape(command)}$/ end diff --git a/spec/unit/reports/http_spec.rb b/spec/unit/reports/http_spec.rb index 13f202607..1a99761d8 100755 --- a/spec/unit/reports/http_spec.rb +++ b/spec/unit/reports/http_spec.rb @@ -15,65 +15,50 @@ describe processor do http.expects(:post).returns(httpok) end - it "should use the reporturl setting's host, port and ssl option" do - uri = URI.parse(Puppet[:reporturl]) - ssl = (uri.scheme == 'https') - Net::HTTP.expects(:new).with( - uri.host, uri.port, optionally(anything, anything) - ).returns http - http.expects(:use_ssl=).with(ssl) - subject.process - end + it "configures the connection for ssl when using https" do + Puppet[:reporturl] = 'https://testing:8080/the/path' - it "uses ssl if reporturl has the https protocol" do - Puppet[:reporturl] = "https://myhost.mydomain:1234/report/upload" - uri = URI.parse(Puppet[:reporturl]) - Net::HTTP.expects(:new).with( - uri.host, uri.port, optionally(anything, anything) + Puppet::Network::HttpPool.expects(:http_instance).with( + 'testing', 8080, true ).returns http - http.expects(:use_ssl=).with(true) + subject.process end - it "does not use ssl if reporturl has plain http protocol" do - Puppet[:reporturl] = "http://myhost.mydomain:1234/report/upload" - uri = URI.parse(Puppet[:reporturl]) - Net::HTTP.expects(:new).with( - uri.host, uri.port, optionally(anything, anything) + it "does not configure the connectino for ssl when using http" do + Puppet[:reporturl] = "http://testing:8080/the/path" + + Puppet::Network::HttpPool.expects(:http_instance).with( + 'testing', 8080, false ).returns http - http.expects(:use_ssl=).with(false) + subject.process end end describe "when making a request" do - let(:http) { stub_everything "http" } + let(:connection) { stub_everything "connection" } let(:httpok) { Net::HTTPOK.new('1.1', 200, '') } before :each do - Net::HTTP.expects(:new).returns(http) + Puppet::Network::HttpPool.expects(:http_instance).returns(connection) end it "should use the path specified by the 'reporturl' setting" do - http.expects(:post).with {|path, data, headers| - path.should == URI.parse(Puppet[:reporturl]).path - }.returns(httpok) + report_path = URI.parse(Puppet[:reporturl]).path + connection.expects(:post).with(report_path, anything, anything).returns(httpok) subject.process end it "should give the body as the report as YAML" do - http.expects(:post).with {|path, data, headers| - data.should == subject.to_yaml - }.returns(httpok) + connection.expects(:post).with(anything, subject.to_yaml, anything).returns(httpok) subject.process end it "should set content-type to 'application/x-yaml'" do - http.expects(:post).with {|path, data, headers| - headers["Content-Type"].should == "application/x-yaml" - }.returns(httpok) + connection.expects(:post).with(anything, anything, has_entry("Content-Type" => "application/x-yaml")).returns(httpok) subject.process end @@ -82,7 +67,7 @@ describe processor do if code.to_i >= 200 and code.to_i < 300 it "should succeed on http code #{code}" do response = klass.new('1.1', code, '') - http.expects(:post).returns(response) + connection.expects(:post).returns(response) Puppet.expects(:err).never subject.process @@ -92,7 +77,7 @@ describe processor do if code.to_i >= 300 && ![301, 302, 307].include?(code.to_i) it "should log error on http code #{code}" do response = klass.new('1.1', code, '') - http.expects(:post).returns(response) + connection.expects(:post).returns(response) Puppet.expects(:err) subject.process diff --git a/spec/unit/reports/store_spec.rb b/spec/unit/reports/store_spec.rb index 5320c6479..421f8404d 100755 --- a/spec/unit/reports/store_spec.rb +++ b/spec/unit/reports/store_spec.rb @@ -47,7 +47,7 @@ describe processor do it "rejects invalid hostnames" do @report.host = ".." - FileTest.expects(:exists?).never + Puppet::FileSystem::File.expects(:exist?).never Tempfile.expects(:new).never expect { @report.process }.to raise_error(ArgumentError, /Invalid node/) end @@ -55,7 +55,7 @@ describe processor do describe "::destroy" do it "rejects invalid hostnames" do - File.expects(:unlink).never + Puppet::FileSystem::File.expects(:unlink).never expect { processor.destroy("..") }.to raise_error(ArgumentError, /Invalid node/) end end diff --git a/spec/unit/resource/catalog_spec.rb b/spec/unit/resource/catalog_spec.rb index dab6e4591..dd88e8e20 100755 --- a/spec/unit/resource/catalog_spec.rb +++ b/spec/unit/resource/catalog_spec.rb @@ -1,5 +1,18 @@ #! /usr/bin/env ruby require 'spec_helper' +require 'puppet_spec/compiler' + +# the json-schema gem doesn't support windows +if not Puppet.features.microsoft_windows? + CATALOG_SCHEMA = JSON.parse(File.read(File.join(File.dirname(__FILE__), '../../../api/schemas/catalog.json'))) + + describe "catalog schema" do + it "should validate against the json meta-schema" do + JSON::Validator.validate!(JSON_META_SCHEMA, CATALOG_SCHEMA) + end + end + +end describe Puppet::Resource::Catalog, "when compiling" do include PuppetSpec::Files @@ -86,26 +99,28 @@ describe Puppet::Resource::Catalog, "when compiling" do it "should accept tags" do config = Puppet::Resource::Catalog.new("mynode") config.tag("one") - config.tags.should == %w{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.tags.should == %w{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.tags.should == %w{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.tags.include?("one").should be_true - config.tags.include?("one::two").should be_true + config.should be_tagged("one") + config.should be_tagged("one::two") end it "should accept classes" do @@ -119,7 +134,7 @@ describe Puppet::Resource::Catalog, "when compiling" do it "should tag itself with passed class names" do config = Puppet::Resource::Catalog.new("mynode") config.add_class("one") - config.tags.should == %w{one} + config.should be_tagged("one") end end @@ -204,12 +219,12 @@ describe Puppet::Resource::Catalog, "when compiling" do @r1 = stub_everything 'r1', :ref => "File[/a]" @r1.stubs(:respond_to?).with(:ref).returns(true) - @r1.stubs(:dup).returns(@r1) + @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(:dup).returns(@r2) + @r2.stubs(:copy_as_resource).returns(@r2) @r2.stubs(:is_a?).with(Puppet::Resource).returns(true) @resources = [@r1,@r2] @@ -720,6 +735,60 @@ describe Puppet::Resource::Catalog, "when compiling" do end end +describe Puppet::Resource::Catalog, "when converting a resource catalog to pson" do + include PuppetSpec::Compiler + + def validate_json_for_catalog(catalog) + JSON::Validator.validate!(CATALOG_SCHEMA, catalog.to_pson) + end + + it "should validate an empty catalog against the schema", :unless => Puppet.features.microsoft_windows? do + empty_catalog = compile_to_catalog("") + validate_json_for_catalog(empty_catalog) + end + + it "should validate a noop catalog against the schema", :unless => Puppet.features.microsoft_windows? do + noop_catalog = compile_to_catalog("create_resources('file', {})") + validate_json_for_catalog(noop_catalog) + end + + it "should validate a single resource catalog against the schema", :unless => Puppet.features.microsoft_windows? do + catalog = compile_to_catalog("create_resources('file', {'/etc/foo'=>{'ensure'=>'present'}})") + validate_json_for_catalog(catalog) + end + + it "should validate a virtual resource catalog against the schema", :unless => Puppet.features.microsoft_windows? do + catalog = compile_to_catalog("create_resources('@file', {'/etc/foo'=>{'ensure'=>'present'}})\nrealize(File['/etc/foo'])") + validate_json_for_catalog(catalog) + end + + it "should validate a single exported resource catalog against the schema", :unless => Puppet.features.microsoft_windows? do + catalog = compile_to_catalog("create_resources('@@file', {'/etc/foo'=>{'ensure'=>'present'}})") + validate_json_for_catalog(catalog) + end + + it "should validate a two resource catalog against the schema", :unless => Puppet.features.microsoft_windows? do + catalog = compile_to_catalog("create_resources('notify', {'foo'=>{'message'=>'one'}, 'bar'=>{'message'=>'two'}})") + validate_json_for_catalog(catalog) + end + + it "should validate a two parameter class catalog against the schema", :unless => Puppet.features.microsoft_windows? 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 + validate_json_for_catalog(catalog) + end +end + describe Puppet::Resource::Catalog, "when converting to pson" do before do @catalog = Puppet::Resource::Catalog.new("myhost") @@ -742,11 +811,11 @@ describe Puppet::Resource::Catalog, "when converting to pson" do PSON.parse @catalog.to_pson end - [:name, :version, :tags, :classes].each do |param| + [: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['data'][param.to_s] == @catalog.send(param) } + pson_output_should { |hash| hash['data'][param.to_s].should == @catalog.send(param) } PSON.parse @catalog.to_pson end end @@ -815,7 +884,8 @@ describe Puppet::Resource::Catalog, "when converting from pson" do it "should set any provided tags on the catalog" do @data['tags'] = %w{one two} PSON.parse @pson.to_pson - @catalog.tags.should == @data['tags'] + @catalog.should be_tagged("one") + @catalog.should be_tagged("two") end it "should set any provided classes on the catalog" do diff --git a/spec/unit/resource/resource_type.json b/spec/unit/resource/resource_type.json deleted file mode 100644 index ffd15d639..000000000 --- a/spec/unit/resource/resource_type.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "type": "object", - "properties": { - "doc": { - "type": "string" - }, - "line": { - "type": "integer" - }, - "file": { - "type": "string" - }, - "parent": { - "type": "string" - }, - "name": { - "type": "string", - "required": "true" - }, - "kind": { - "type": "string", - "enum": [ - "class", - "node", - "defined_type" - ], - "required": "true" - }, - "parameters": { - "type": "object" - } - }, - "additionalProperties": false -} diff --git a/spec/unit/resource/status_spec.rb b/spec/unit/resource/status_spec.rb index f3cc5699c..d50abdce3 100755 --- a/spec/unit/resource/status_spec.rb +++ b/spec/unit/resource/status_spec.rb @@ -59,7 +59,9 @@ describe Puppet::Resource::Status do it "should copy the resource's tags" do @resource.expects(:tags).returns %w{foo bar} - Puppet::Resource::Status.new(@resource).tags.should == %w{foo bar} + status = Puppet::Resource::Status.new(@resource) + status.should be_tagged("foo") + status.should be_tagged("bar") end it "should always convert the resource to a string" do @@ -113,6 +115,14 @@ describe Puppet::Resource::Status do @status.events.should == [event] end + it "records an event for a failure caused by an error" do + @status.failed_because(StandardError.new("the message")) + + expect(@status.events[0].message).to eq("the message") + expect(@status.events[0].status).to eq("failure") + expect(@status.events[0].name).to eq(:resource_error) + end + it "should count the number of successful events and set changed" do 3.times{ @status << Puppet::Transaction::Event.new(:status => 'success') } @status.change_count.should == 3 diff --git a/spec/unit/resource/type_spec.rb b/spec/unit/resource/type_spec.rb index a4929208e..1b5a4727b 100755 --- a/spec/unit/resource/type_spec.rb +++ b/spec/unit/resource/type_spec.rb @@ -1,8 +1,19 @@ #! /usr/bin/env ruby require 'spec_helper' - require 'puppet/resource/type' +# the json-schema gem doesn't support windows +if not Puppet.features.microsoft_windows? + RESOURCE_TYPE_SCHEMA = JSON.parse(File.read(File.join(File.dirname(__FILE__), '../../../api/schemas/resource_type.json'))) + + describe "resource type schema" do + it "should validate against the json meta-schema" do + JSON::Validator.validate!(JSON_META_SCHEMA, RESOURCE_TYPE_SCHEMA) + end + end + +end + describe Puppet::Resource::Type do it "should have a 'name' attribute" do Puppet::Resource::Type.new(:hostclass, "foo").name.should == "foo" @@ -31,6 +42,10 @@ describe Puppet::Resource::Type do end describe "when converting to json" do + def validate_json_for_type(type) + JSON::Validator.validate!(RESOURCE_TYPE_SCHEMA, type.to_pson) + end + before do @type = Puppet::Resource::Type.new(:hostclass, "foo") end @@ -48,6 +63,20 @@ describe Puppet::Resource::Type do double_convert.type.should == @type.type end + it "should validate with only name and kind", :unless => Puppet.features.microsoft_windows? do + validate_json_for_type(@type) + end + + it "should validate with all fields set", :unless => Puppet.features.microsoft_windows? 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" + + validate_json_for_type(@type) + end + it "should include any arguments" do @type.set_arguments("one" => nil, "two" => "foo") diff --git a/spec/unit/resource_spec.rb b/spec/unit/resource_spec.rb index 37d2001b8..a8f21d205 100755 --- a/spec/unit/resource_spec.rb +++ b/spec/unit/resource_spec.rb @@ -339,8 +339,11 @@ describe Puppet::Resource do end it "should query the injector using a namespaced key" do - compiler.injector.expects(:lookup).with(scope, 'apache::port') + compiler.injector.expects(:lookup).with(scope, 'apache::port').returns("8081") + resource.set_default_parameters(scope) + + resource[:port].should == "8081" end it "should use the value from the data_binding terminus" do @@ -376,8 +379,16 @@ describe Puppet::Resource do 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 + it "should use the default value if the injector returns nil" do compiler.injector.expects(:lookup).returns(nil) + Puppet::DataBinding.indirection.expects(:find).returns(nil) resource.set_default_parameters(scope) @@ -607,7 +618,7 @@ describe Puppet::Resource do end end - describe "when serializing" do + describe "when serializing a native type" do before do @resource = Puppet::Resource.new("file", "/my/file") @resource["one"] = "test" @@ -622,6 +633,31 @@ describe Puppet::Resource do end end + describe "when serializing a defined type" do + before do + type = Puppet::Resource::Type.new(:definition, "foo::bar") + Puppet::Node::Environment.new.known_resource_types.add type + end + + before :each do + @resource = Puppet::Resource.new('foo::bar', 'xyzzy') + @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 yaml object" do + text = @resource.render('yaml') + + newresource = Puppet::Resource.convert_from('yaml', text) + newresource.should equal_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") @@ -810,9 +846,9 @@ describe Puppet::Resource do end end - describe "it should implement to_resource" do + describe "it should implement copy_as_resource" do resource = Puppet::Resource.new("file", "/my/file") - resource.to_resource.should == resource + resource.copy_as_resource.should == resource end describe "because it is an indirector model" do diff --git a/spec/unit/settings/autosign_setting_spec.rb b/spec/unit/settings/autosign_setting_spec.rb new file mode 100644 index 000000000..c08171374 --- /dev/null +++ b/spec/unit/settings/autosign_setting_spec.rb @@ -0,0 +1,46 @@ +require 'spec_helper' + +require 'puppet/settings' +require 'puppet/settings/autosign_setting' + +describe Puppet::Settings::AutosignSetting do + let(:setting) { described_class.new(:settings => mock('settings'), :desc => "test") } + + it "is of type :autosign" do + expect(setting.type).to eq :autosign + end + + describe "when munging the setting" do + it "passes boolean values through" do + expect(setting.munge(true)).to eq true + expect(setting.munge(false)).to eq false + end + + it "converts nil to false" do + expect(setting.munge(nil)).to eq false + end + + it "munges string 'true' to boolean true" do + expect(setting.munge('true')).to eq true + end + + it "munges string 'false' to boolean false" do + expect(setting.munge('false')).to eq false + end + + it "passes absolute paths through" do + path = File.expand_path('/path/to/autosign.conf') + expect(setting.munge(path)).to eq path + end + + it "fails if given anything else" do + cases = [1.0, 'sometimes', 'relative/autosign.conf'] + + cases.each do |invalid| + expect { + setting.munge(invalid) + }.to raise_error Puppet::Settings::ValidationError, /Invalid autosign value/ + end + end + end +end diff --git a/spec/unit/settings/file_setting_spec.rb b/spec/unit/settings/file_setting_spec.rb index a6e3f3f12..d9c6f5629 100755 --- a/spec/unit/settings/file_setting_spec.rb +++ b/spec/unit/settings/file_setting_spec.rb @@ -139,14 +139,14 @@ describe Puppet::Settings::FileSetting do it "should skip non-existent files if 'create_files' is not enabled" do @file.expects(:create_files?).returns false @file.expects(:type).returns :file - File.expects(:exist?).with(@basepath).returns false + Puppet::FileSystem::File.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 - File.expects(:exist?).with(@basepath).returns true + Puppet::FileSystem::File.expects(:exist?).with(@basepath).returns true @file.to_resource.should be_instance_of(Puppet::Resource) end diff --git a/spec/unit/settings/path_setting_spec.rb b/spec/unit/settings/path_setting_spec.rb index d2ec46b5d..b7bc3198c 100755 --- a/spec/unit/settings/path_setting_spec.rb +++ b/spec/unit/settings/path_setting_spec.rb @@ -22,8 +22,8 @@ describe Puppet::Settings::PathSetting do end it "should work with UNC paths" do - subject.munge('//server/some/path').should == '//server/some/path' - subject.munge('\\\\server\some\path').should == '//server/some/path' + subject.munge('//localhost/some/path').should == '//localhost/some/path' + subject.munge('\\\\localhost\some\path').should == '//localhost/some/path' end end end diff --git a/spec/unit/settings/priority_setting_spec.rb b/spec/unit/settings/priority_setting_spec.rb new file mode 100755 index 000000000..d51e39dc4 --- /dev/null +++ b/spec/unit/settings/priority_setting_spec.rb @@ -0,0 +1,66 @@ +#!/usr/bin/env ruby +require 'spec_helper' + +require 'puppet/settings' +require 'puppet/settings/priority_setting' +require 'puppet/util/platform' + +describe Puppet::Settings::PrioritySetting do + let(:setting) { described_class.new(:settings => mock('settings'), :desc => "test") } + + it "is of type :priority" do + setting.type.should == :priority + end + + describe "when munging the setting" do + it "passes nil through" do + setting.munge(nil).should be_nil + end + + it "returns the same value if given an integer" do + setting.munge(5).should == 5 + end + + it "returns an integer if given a decimal string" do + setting.munge('12').should == 12 + end + + it "returns a negative integer if given a negative integer string" do + setting.munge('-5').should == -5 + end + + it "fails if given anything else" do + [ 'foo', 'realtime', true, 8.3, [] ].each do |value| + expect { + setting.munge(value) + }.to raise_error(Puppet::Settings::ValidationError) + end + end + + describe "on a Unix-like platform it", :unless => Puppet::Util::Platform.windows? do + it "parses high, normal, low, and idle priorities" do + { + 'high' => -10, + 'normal' => 0, + 'low' => 10, + 'idle' => 19 + }.each do |value, converted_value| + setting.munge(value).should == converted_value + end + end + end + + describe "on a Windows-like platform it", :if => Puppet::Util::Platform.windows? do + it "parses high, normal, low, and idle priorities" do + { + 'high' => Process::HIGH_PRIORITY_CLASS, + 'normal' => Process::NORMAL_PRIORITY_CLASS, + 'low' => Process::BELOW_NORMAL_PRIORITY_CLASS, + 'idle' => Process::IDLE_PRIORITY_CLASS + }.each do |value, converted_value| + setting.munge(value).should == converted_value + end + end + end + end +end diff --git a/spec/unit/settings_spec.rb b/spec/unit/settings_spec.rb index 12107bcb7..b3dc31f3d 100755 --- a/spec/unit/settings_spec.rb +++ b/spec/unit/settings_spec.rb @@ -485,7 +485,7 @@ describe Puppet::Settings do :three => { :default => "$one $two THREE", :desc => "c"}, :four => { :default => "$two $three FOUR", :desc => "d"}, :five => { :default => nil, :desc => "e" } - FileTest.stubs(:exist?).returns true + Puppet::FileSystem::File.stubs(:exist?).returns true end describe "call_on_define" do @@ -589,7 +589,7 @@ describe Puppet::Settings do :config => { :type => :file, :default => "/my/file", :desc => "a" }, :one => { :default => "ONE", :desc => "a" }, :two => { :default => "TWO", :desc => "b" } - FileTest.stubs(:exist?).returns true + Puppet::FileSystem::File.stubs(:exist?).returns true @settings.preferred_run_mode = :agent end @@ -666,8 +666,8 @@ describe Puppet::Settings do 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) - FileTest.expects(:exist?).with(main_config_file_default_location).returns(false) - FileTest.expects(:exist?).with(user_config_file_default_location).never + Puppet::FileSystem::File.expects(:exist?).with(main_config_file_default_location).returns(false) + Puppet::FileSystem::File.expects(:exist?).with(user_config_file_default_location).never @settings.send(:parse_config_files) end @@ -678,7 +678,7 @@ describe Puppet::Settings do Puppet.features.stubs(:root?).returns(false) seq = sequence "load config files" - FileTest.expects(:exist?).with(user_config_file_default_location).returns(false).in_sequence(seq) + Puppet::FileSystem::File.expects(:exist?).with(user_config_file_default_location).returns(false).in_sequence(seq) @settings.send(:parse_config_files) end @@ -699,8 +699,8 @@ describe Puppet::Settings do :two => { :default => "$one TWO", :desc => "b" }, :three => { :default => "$one $two THREE", :desc => "c" } @settings.stubs(:user_config_file).returns(@userconfig) - FileTest.stubs(:exist?).with(@file).returns true - FileTest.stubs(:exist?).with(@userconfig).returns false + Puppet::FileSystem::File.stubs(:exist?).with(@file).returns true + Puppet::FileSystem::File.stubs(:exist?).with(@userconfig).returns false end it "should not ignore the report setting" do @@ -712,7 +712,7 @@ describe Puppet::Settings do [puppetd] report=true CONF - FileTest.expects(:exist?).with(myfile).returns(true) + Puppet::FileSystem::File.expects(:exist?).with(myfile).returns(true) @settings.expects(:read_file).returns(text) @settings.send(:parse_config_files) @settings[:report].should be_true @@ -722,7 +722,7 @@ describe Puppet::Settings 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 - FileTest.expects(:exist?).with(myfile).returns(true) + Puppet::FileSystem::File.expects(:exist?).with(myfile).returns(true) File.expects(:read).with(myfile).returns "[main]" @@ -730,7 +730,7 @@ describe Puppet::Settings do end it "should not try to parse non-existent files" do - FileTest.expects(:exist?).with(@file).returns false + Puppet::FileSystem::File.expects(:exist?).with(@file).returns false File.expects(:read).with(@file).never @@ -881,6 +881,33 @@ describe Puppet::Settings do 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 + + @settings.initialize_app_defaults(:logdir => '/path/to/logdir', :confdir => '/path/to/confdir', :vardir => '/path/to/vardir') + + hook_invoked.should be_true + @settings[:deferred].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" } @@ -925,7 +952,7 @@ describe Puppet::Settings do context "running non-root without explicit config file" do before :each do Puppet.features.stubs(:root?).returns(false) - FileTest.expects(:exist?). + Puppet::FileSystem::File.expects(:exist?). with(user_config_file_default_location). returns(true).in_sequence(seq) @settings.expects(:read_file). @@ -947,7 +974,7 @@ describe Puppet::Settings do context "running as root without explicit config file" do before :each do Puppet.features.stubs(:root?).returns(true) - FileTest.expects(:exist?). + Puppet::FileSystem::File.expects(:exist?). with(main_config_file_default_location). returns(true).in_sequence(seq) @settings.expects(:read_file). @@ -970,7 +997,7 @@ describe Puppet::Settings do before :each do Puppet.features.stubs(:root?).returns(false) @settings[:confdir] = File.dirname(main_config_file_default_location) - FileTest.expects(:exist?). + Puppet::FileSystem::File.expects(:exist?). with(main_config_file_default_location). returns(true).in_sequence(seq) @settings.expects(:read_file). @@ -1000,13 +1027,13 @@ describe Puppet::Settings do :one => { :default => "ONE", :desc => "a" }, :two => { :default => "$one TWO", :desc => "b" }, :three => { :default => "$one $two THREE", :desc => "c" } - FileTest.stubs(:exist?).with(@file).returns true - FileTest.stubs(:exist?).with(@userconfig).returns false + Puppet::FileSystem::File.stubs(:exist?).with(@file).returns true + Puppet::FileSystem::File.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 - FileTest.expects(:exist?).with(@file).returns false + Puppet::FileSystem::File.expects(:exist?).with(@file).returns false Puppet::Util::WatchedFile.expects(:new).never @settings.expects(:parse_config_files).never @@ -1285,10 +1312,6 @@ describe Puppet::Settings do @settings.define_settings :files, :myfile => {:type => :file, :default => make_absolute("/myfile"), :desc => "a", :mode => 0755} end - it "should provide a method that writes files with the correct modes" do - @settings.should respond_to(:write) - end - it "should provide a method that creates directories with the correct modes" do Puppet::Util::SUIDManager.expects(:asuser).with("suser", "sgroup").yields Dir.expects(:mkdir).with(make_absolute("/otherdir"), 0755) @@ -1567,17 +1590,6 @@ describe Puppet::Settings do end end - describe "#writesub" do - it "should only pass valid arguments to File.open" do - settings = Puppet::Settings.new - settings.stubs(:get_config_file_default).with(:privatekeydir).returns(OpenStruct.new(:mode => "750")) - - File.expects(:open).with("/path/to/keydir", "w", 750).returns true - settings.writesub(:privatekeydir, "/path/to/keydir") - end - end - - describe "when dealing with command-line options" do let(:settings) { Puppet::Settings.new } diff --git a/spec/unit/ssl/certificate_authority/autosign_command_spec.rb b/spec/unit/ssl/certificate_authority/autosign_command_spec.rb new file mode 100644 index 000000000..5792bb4f5 --- /dev/null +++ b/spec/unit/ssl/certificate_authority/autosign_command_spec.rb @@ -0,0 +1,30 @@ +require 'spec_helper' + +require 'puppet/ssl/certificate_authority/autosign_command' + +describe Puppet::SSL::CertificateAuthority::AutosignCommand do + + let(:csr) { stub 'csr', :name => 'host', :to_s => 'CSR PEM goes here' } + let(:decider) { Puppet::SSL::CertificateAuthority::AutosignCommand.new('/autosign/command') } + + it "returns true if the command succeeded" do + executes_the_command_resulting_in(0) + + decider.allowed?(csr).should == true + end + + it "returns false if the command failed" do + executes_the_command_resulting_in(1) + + decider.allowed?(csr).should == false + end + + def executes_the_command_resulting_in(exitstatus) + Puppet::Util::Execution.expects(:execute). + with(['/autosign/command', 'host'], + has_entries(:stdinfile => anything, + :combine => true, + :failonfail => false)). + returns(Puppet::Util::Execution::ProcessOutput.new('', exitstatus)) + end +end diff --git a/spec/unit/ssl/certificate_authority_spec.rb b/spec/unit/ssl/certificate_authority_spec.rb index 70186a137..13c169a0a 100755 --- a/spec/unit/ssl/certificate_authority_spec.rb +++ b/spec/unit/ssl/certificate_authority_spec.rb @@ -92,12 +92,6 @@ describe Puppet::SSL::CertificateAuthority do Puppet::SSL::CertificateAuthority.new end - it "should create an inventory instance" do - Puppet::SSL::Inventory.expects(:new).returns "inventory" - - Puppet::SSL::CertificateAuthority.new.inventory.should == "inventory" - end - it "should make sure the CA is set up" do Puppet::SSL::CertificateAuthority.any_instance.expects(:setup) @@ -171,16 +165,16 @@ describe Puppet::SSL::CertificateAuthority do it "should create and store a password at :capass" do Puppet[:capass] = File.expand_path("/path/to/pass") - FileTest.expects(:exist?).with(Puppet[:capass]).returns false + Puppet::FileSystem::File.expects(:exist?).with(Puppet[:capass]).returns false - fh = mock 'filehandle' - Puppet.settings.expects(:write).with(:capass).yields fh - - fh.expects(:print).with { |s| s.length > 18 } + 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 @@ -238,11 +232,10 @@ describe Puppet::SSL::CertificateAuthority do Puppet::SSL::Certificate.stubs(:new).returns @cert - @cert.stubs(:content=) Puppet::SSL::Certificate.indirection.stubs(:save) # Stub out the factory - Puppet::SSL::CertificateFactory.stubs(:build).returns "my real cert" + 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 @@ -255,39 +248,6 @@ describe Puppet::SSL::CertificateAuthority do Puppet::SSL::CertificateRequest.indirection.stubs(:destroy) end - describe "and calculating the next certificate serial number" do - before do - @path = File.expand_path("/path/to/serial") - Puppet[:serial] = @path - - @filehandle = stub 'filehandle', :<< => @filehandle - Puppet.settings.stubs(:readwritelock).with(:serial).yields @filehandle - end - - it "should default to 0x1 for the first serial number" do - @ca.next_serial.should == 0x1 - end - - it "should return the current content of the serial file" do - FileTest.stubs(:exist?).with(@path).returns true - File.expects(:read).with(@path).returns "0002" - - @ca.next_serial.should == 2 - end - - it "should write the next serial number to the serial file as hex" do - @filehandle.expects(:<<).with("0002") - - @ca.next_serial - end - - it "should lock the serial file while writing" do - Puppet.settings.expects(:readwritelock).with(:serial) - - @ca.next_serial - end - end - describe "its own certificate" do before do @serial = 10 @@ -303,28 +263,28 @@ describe Puppet::SSL::CertificateAuthority do it "should use a certificate type of :ca" do Puppet::SSL::CertificateFactory.expects(:build).with do |*args| args[0].should == :ca - end.returns "my real cert" + 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 "my real cert" + 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 "my real cert" + 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 "my real cert" + end.returns @cert.content @ca.sign(@name, :ca, @request) end @@ -355,7 +315,7 @@ describe Puppet::SSL::CertificateAuthority do it "should use a certificate type of :server" do Puppet::SSL::CertificateFactory.expects(:build).with do |*args| args[0] == :server - end.returns "my real cert" + end.returns @cert.content @ca.sign(@name) end @@ -404,14 +364,14 @@ describe Puppet::SSL::CertificateAuthority do it "should use the CA certificate as the issuer" do Puppet::SSL::CertificateFactory.expects(:build).with do |*args| args[2] == @cacert.content - end.returns "my real cert" - @ca.sign(@name) + end.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 "my real cert" + end.returns @cert.content @ca.sign(@name) end @@ -518,6 +478,40 @@ describe Puppet::SSL::CertificateAuthority do 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", @@ -610,76 +604,104 @@ describe Puppet::SSL::CertificateAuthority do end describe "when autosigning certificates" do - let(:autosign) { File.expand_path("/auto/sign") } - it "should do nothing if autosign is disabled" do - Puppet[:autosign] = 'false' + let(:csr) { Puppet::SSL::CertificateRequest.new("host") } - Puppet::SSL::CertificateRequest.indirection.expects(:search).never - @ca.autosign - end + describe "using the autosign setting" do + let(:autosign) { File.expand_path("/auto/sign") } - it "should do nothing if no autosign.conf exists" do - Puppet[:autosign] = autosign - FileTest.expects(:exist?).with(autosign).returns false + it "should do nothing if autosign is disabled" do + Puppet[:autosign] = false - Puppet::SSL::CertificateRequest.indirection.expects(:search).never - @ca.autosign - end + @ca.expects(:sign).never + @ca.autosign(csr) + end - describe "and autosign is enabled and the autosign.conf file exists" do - before do + it "should do nothing if no autosign.conf exists" do Puppet[:autosign] = autosign - FileTest.stubs(:exist?).with(autosign).returns true - File.stubs(:readlines).with(autosign).returns ["one\n", "two\n"] + non_existent_file = Puppet::FileSystem::MemoryFile.a_missing_file(autosign) + Puppet::FileSystem::File.overlay(non_existent_file) do + @ca.expects(:sign).never + @ca.autosign(csr) + end + end - Puppet::SSL::CertificateRequest.indirection.stubs(:search).returns [] + describe "and autosign is enabled and the autosign.conf file exists" do + let(:store) { stub 'store', :allow => nil, :allowed? => false } - @store = stub 'store', :allow => nil - Puppet::Network::AuthStore.stubs(:new).returns @store - end + 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::Network::AuthStore.expects(:new).returns @store + 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::File.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") + store.expects(:allow).with("one") + store.expects(:allow).with("two") - @ca.autosign - end + @ca.autosign(csr) + end + end - it "should reparse the autosign configuration on each call" do - Puppet::Network::AuthStore.expects(:new).times(2).returns @store + it "should reparse the autosign configuration on each call" do + Puppet::FileSystem::File.overlay(Puppet::FileSystem::MemoryFile.a_regular_file_containing(autosign, "one")) do + Puppet::Network::AuthStore.stubs(:new).times(2).returns store - @ca.autosign - @ca.autosign - end + @ca.autosign(csr) + @ca.autosign(csr) + end + end - it "should ignore comments" do - File.stubs(:readlines).with(autosign).returns ["one\n", "#two\n"] + it "should ignore comments" do + Puppet::FileSystem::File.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 - end + store.expects(:allow).with("one") - it "should ignore blank lines" do - File.stubs(:readlines).with(autosign).returns ["one\n", "\n"] + @ca.autosign(csr) + end + end - @store.expects(:allow).with("one") - @ca.autosign + it "should ignore blank lines" do + Puppet::FileSystem::File.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 "should sign all CSRs whose hostname matches the autosign configuration" do - csr1 = mock 'csr1' - csr2 = mock 'csr2' - Puppet::SSL::CertificateRequest.indirection.stubs(:search).returns [csr1, csr2] + it "autosigns the CSR if the autosign command returned true" do + Puppet::FileSystem::File.overlay(autosign_executable) do + autosign_cmd.expects(:allowed?).with(csr).returns true + + @ca.expects(:sign).with('host') + @ca.autosign(csr) + end end - it "should not sign CSRs whose hostname does not match the autosign configuration" do - csr1 = mock 'csr1' - csr2 = mock 'csr2' - Puppet::SSL::CertificateRequest.indirection.stubs(:search).returns [csr1, csr2] + it "doesn't autosign the CSR if the autosign_command returned false" do + Puppet::FileSystem::File.overlay(autosign_executable) do + autosign_cmd.expects(:allowed?).with(csr).returns false + + @ca.expects(:sign).never + @ca.autosign(csr) + end end end end @@ -701,28 +723,6 @@ describe Puppet::SSL::CertificateAuthority do @ca = Puppet::SSL::CertificateAuthority.new end - it "should have a method for acting on the SSL files" do - @ca.should respond_to(:apply) - end - - describe "when applying a method to a set of hosts" do - it "should fail if no subjects have been specified" do - expect { @ca.apply(:generate) }.to raise_error(ArgumentError) - end - - it "should create an Interface instance with the specified method and the options" do - Puppet::SSL::CertificateAuthority::Interface.expects(:new).with(:generate, :to => :host).returns(stub('applier', :apply => nil)) - @ca.apply(:generate, :to => :host) - end - - it "should apply the Interface with itself as the argument" do - applier = stub('applier') - applier.expects(:apply).with(@ca) - Puppet::SSL::CertificateAuthority::Interface.expects(:new).returns applier - @ca.apply(:generate, :to => :ca_testing) - end - end - it "should be able to list waiting certificate requests" do req1 = stub 'req1', :name => "one" req2 = stub 'req2', :name => "two" @@ -979,16 +979,15 @@ require 'puppet/indirector/memory' describe "CertificateAuthority.generate" do def expect_to_increment_serial_file - Puppet.settings.expects(:readwritelock).with(:serial) + Puppet.settings.setting(:serial).expects(:exclusive_open) end def expect_to_sign_a_cert expect_to_increment_serial_file - Puppet.settings.expects(:write).with(:cert_inventory, "a") end def expect_to_write_the_ca_password - Puppet.settings.expects(:write).with(:capass) + Puppet.settings.setting(:capass).expects(:open).with('w') end def expect_ca_initialization @@ -996,10 +995,6 @@ describe "CertificateAuthority.generate" do expect_to_sign_a_cert end - def avoid_rebuilding_inventory_for_every_cert - Puppet::SSL::Inventory.any_instance.stubs(:rebuild) - end - INDIRECTED_CLASSES = [ Puppet::SSL::Certificate, Puppet::SSL::CertificateRequest, @@ -1021,7 +1016,7 @@ describe "CertificateAuthority.generate" do end before do - avoid_rebuilding_inventory_for_every_cert + Puppet::SSL::Inventory.stubs(:new).returns(stub("Inventory", :add => nil)) INDIRECTED_CLASSES.each { |const| const.indirection.terminus_class = :memory } end @@ -1036,7 +1031,7 @@ describe "CertificateAuthority.generate" do let(:ca) { Puppet::SSL::CertificateAuthority.new } before do - expect_ca_initialization + expect_ca_initialization end it "should fail if a certificate already exists for the host" do diff --git a/spec/unit/ssl/certificate_factory_spec.rb b/spec/unit/ssl/certificate_factory_spec.rb index 85609593a..fa436edcf 100755 --- a/spec/unit/ssl/certificate_factory_spec.rb +++ b/spec/unit/ssl/certificate_factory_spec.rb @@ -115,6 +115,24 @@ describe Puppet::SSL::CertificateFactory do end end + it "can add custom extension requests" do + csr = Puppet::SSL::CertificateRequest.new(name) + csr.generate(key) + + csr.stubs(:request_extensions).returns([ + {'oid' => '1.3.6.1.4.1.34380.1.2.1', 'value' => 'some-value'}, + {'oid' => 'pp_uuid', 'value' => 'some-uuid'}, + ]) + + cert = subject.build(:client, csr, issuer, serial) + + priv_ext = cert.extensions.find {|ext| ext.oid == '1.3.6.1.4.1.34380.1.2.1'} + uuid_ext = cert.extensions.find {|ext| ext.oid == 'pp_uuid'} + + expect(priv_ext.value).to eq 'some-value' + expect(uuid_ext.value).to eq 'some-uuid' + end + # Can't check the CA here, since that requires way more infrastructure # that I want to build up at this time. We can verify the critical # values, though, which are non-CA certs. --daniel 2011-10-11 diff --git a/spec/unit/ssl/certificate_request_attributes_spec.rb b/spec/unit/ssl/certificate_request_attributes_spec.rb new file mode 100644 index 000000000..6165330aa --- /dev/null +++ b/spec/unit/ssl/certificate_request_attributes_spec.rb @@ -0,0 +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 + end + + context "with an available attributes file" do + before do + Puppet::FileSystem::File.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 + 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 1dc449d53..192c929b5 100755 --- a/spec/unit/ssl/certificate_request_spec.rb +++ b/spec/unit/ssl/certificate_request_spec.rb @@ -178,6 +178,109 @@ describe Puppet::SSL::CertificateRequest do 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 + 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 + 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 diff --git a/spec/unit/ssl/certificate_spec.rb b/spec/unit/ssl/certificate_spec.rb index 70d35d4c7..c8f9930f3 100755 --- a/spec/unit/ssl/certificate_spec.rb +++ b/spec/unit/ssl/certificate_spec.rb @@ -75,6 +75,17 @@ describe Puppet::SSL::Certificate do end describe "when managing instances" do + + def build_cert(opts) + key = Puppet::SSL::Key.new('quux') + key.generate + csr = Puppet::SSL::CertificateRequest.new('quux') + csr.generate(key, opts) + + raw_cert = Puppet::SSL::CertificateFactory.build('client', csr, csr.content, 14) + @class.from_instance(raw_cert) + end + before do @certificate = @class.new("myname") end @@ -93,33 +104,35 @@ describe Puppet::SSL::Certificate do describe "#subject_alt_names" do it "should list all alternate names when the extension is present" do - key = Puppet::SSL::Key.new('quux') - key.generate - - csr = Puppet::SSL::CertificateRequest.new('quux') - csr.generate(key, :dns_alt_names => 'foo, bar,baz') - - raw_csr = csr.content - - cert = Puppet::SSL::CertificateFactory.build('server', csr, raw_csr, 14) - certificate = @class.from_s(cert.to_pem) + certificate = build_cert(:dns_alt_names => 'foo, bar,baz') certificate.subject_alt_names. should =~ ['DNS:foo', 'DNS:bar', 'DNS:baz', 'DNS:quux'] end it "should return an empty list of names if the extension is absent" do - key = Puppet::SSL::Key.new('quux') - key.generate + certificate = build_cert({}) + certificate.subject_alt_names.should be_empty + end + end - csr = Puppet::SSL::CertificateRequest.new('quux') - csr.generate(key) + describe "custom extensions" do + it "returns extensions under the ppRegCertExt" do + exts = {'pp_uuid' => 'abcdfd'} + cert = build_cert(:extension_requests => exts) + expect(cert.custom_extensions).to include('oid' => 'pp_uuid', 'value' => 'abcdfd') + end - raw_csr = csr.content + it "returns extensions under the ppPrivCertExt" do + exts = {'1.3.6.1.4.1.34380.1.2.1' => 'x509 :('} + cert = build_cert(:extension_requests => exts) + expect(cert.custom_extensions).to include('oid' => '1.3.6.1.4.1.34380.1.2.1', 'value' => 'x509 :(') + end - cert = Puppet::SSL::CertificateFactory.build('client', csr, raw_csr, 14) - certificate = @class.from_s(cert.to_pem) - certificate.subject_alt_names.should be_empty + it "doesn't return standard extensions" do + cert = build_cert(:dns_alt_names => 'foo') + expect(cert.custom_extensions).to be_empty end + end it "should return a nil expiration if there is no actual certificate" do diff --git a/spec/unit/ssl/host_spec.rb b/spec/unit/ssl/host_spec.rb index 8240991c7..3b341cc4e 100755 --- a/spec/unit/ssl/host_spec.rb +++ b/spec/unit/ssl/host_spec.rb @@ -9,9 +9,24 @@ def base_pson_comparison(result, pson_hash) result["state"].should == pson_hash["desired_state"] end +# the json-schema gem doesn't support windows +if not Puppet.features.microsoft_windows? + HOST_SCHEMA = JSON.parse(File.read(File.join(File.dirname(__FILE__), '../../../api/schemas/host.json'))) + + describe "host schema" do + it "should validate against the json meta-schema" do + JSON::Validator.validate!(JSON_META_SCHEMA, HOST_SCHEMA) + end + end +end + describe Puppet::SSL::Host do include PuppetSpec::Files + def validate_json_for_host(host) + JSON::Validator.validate!(HOST_SCHEMA, host.to_pson) + end + before do Puppet::SSL::Host.indirection.terminus_class = :file @@ -823,7 +838,7 @@ describe Puppet::SSL::Host do let(:host) do Puppet::SSL::Host.new("bazinga") end - + let(:pson_hash) do { "fingerprint" => host.certificate_request.fingerprint, @@ -831,15 +846,20 @@ describe Puppet::SSL::Host do "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", :unless => Puppet.features.microsoft_windows? do + host.generate_certificate_request + validate_json_for_host(host) + end + describe "explicit fingerprints" do [:SHA1, :SHA256, :SHA512].each do |md| it "should include #{md}" do @@ -854,7 +874,7 @@ describe Puppet::SSL::Host do 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 @@ -867,22 +887,28 @@ describe Puppet::SSL::Host do end end - [ "", + [ "", "test, alt, names" ].each do |alt_names| describe "when #{alt_names}" do - it "should include the dns_alt_names associated with the certificate" 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", :unless => Puppet.features.microsoft_windows? do + validate_json_for_host(host) + end end end end - it "should be able to identify a host with a signed certificate" do host.generate_certificate_request diff --git a/spec/unit/ssl/inventory_spec.rb b/spec/unit/ssl/inventory_spec.rb index b145cd9ab..9bcbbcea5 100755 --- a/spec/unit/ssl/inventory_spec.rb +++ b/spec/unit/ssl/inventory_spec.rb @@ -20,7 +20,7 @@ describe Puppet::SSL::Inventory, :unless => Puppet.features.microsoft_windows? d before do Puppet[:cert_inventory] = cert_inventory - FileTest.stubs(:exist?).with(cert_inventory).returns true + Puppet::FileSystem::File.stubs(:exist?).with(cert_inventory).returns true @inventory = @class.new @@ -28,86 +28,51 @@ describe Puppet::SSL::Inventory, :unless => Puppet.features.microsoft_windows? d end describe "and creating the inventory file" do - before do - Puppet.settings.stubs(:write) - FileTest.stubs(:exist?).with(cert_inventory).returns false - - Puppet::SSL::Certificate.indirection.stubs(:search).returns [] - end - - it "should log that it is building a new inventory file" do - Puppet.expects(:notice) - - @inventory.rebuild - end - - it "should use the Settings to write to the file" do - Puppet.settings.expects(:write).with(:cert_inventory) - - @inventory.rebuild - end - - it "should add a header to the file" do - fh = mock 'filehandle' - Puppet.settings.stubs(:write).yields fh - fh.expects(:print).with { |str| str =~ /^#/ } - - @inventory.rebuild - end - - it "should add formatted information on all existing certificates" do - cert1 = mock 'cert1' - cert2 = mock 'cert2' - + it "re-adds all of the existing certificates" do + inventory_file = StringIO.new + Puppet.settings.setting(:cert_inventory).stubs(:open).yields(inventory_file) + + cert1 = Puppet::SSL::Certificate.new("cert1") + cert1.content = stub 'cert1', + :serial => 2, + :not_before => Time.now, + :not_after => Time.now, + :subject => "/CN=smocking" + cert2 = Puppet::SSL::Certificate.new("cert2") + cert2.content = stub 'cert2', + :serial => 3, + :not_before => Time.now, + :not_after => Time.now, + :subject => "/CN=mocking bird" Puppet::SSL::Certificate.indirection.expects(:search).with("*").returns [cert1, cert2] - @class.any_instance.expects(:add).with(cert1) - @class.any_instance.expects(:add).with(cert2) - @inventory.rebuild + + expect(inventory_file.string).to match(/\/CN=smocking/) + expect(inventory_file.string).to match(/\/CN=mocking bird/) end end describe "and adding a certificate" do - it "should build the inventory file if one does not exist" do - Puppet[:cert_inventory] = cert_inventory - Puppet.settings.stubs(:write) - - FileTest.expects(:exist?).with(cert_inventory).returns false - - @inventory.expects(:rebuild) - - @inventory.add(@cert) - end it "should use the Settings to write to the file" do - Puppet.settings.expects(:write).with(:cert_inventory, "a") + Puppet.settings.setting(:cert_inventory).expects(:open).with("a") @inventory.add(@cert) end - it "should use the actual certificate if it was passed a Puppet certificate" do + it "should add formatted certificate information to the end of the file" do cert = Puppet::SSL::Certificate.new("mycert") cert.content = @cert - fh = stub 'filehandle', :print => nil - Puppet.settings.stubs(:write).yields fh - - @inventory.expects(:format).with(@cert) - - @inventory.add(@cert) - end - - it "should add formatted certificate information to the end of the file" do - fh = mock 'filehandle' - - Puppet.settings.stubs(:write).yields fh + fh = StringIO.new + Puppet.settings.setting(:cert_inventory).expects(:open).with("a").yields(fh) @inventory.expects(:format).with(@cert).returns "myformat" - fh.expects(:print).with("myformat") - @inventory.add(@cert) + + expect(fh.string).to eq("myformat") end end @@ -152,7 +117,7 @@ describe Puppet::SSL::Inventory, :unless => Puppet.features.microsoft_windows? d describe "and finding a serial number" do it "should return nil if the inventory file is missing" do - FileTest.expects(:exist?).with(cert_inventory).returns false + Puppet::FileSystem::File.expects(:exist?).with(cert_inventory).returns false @inventory.serial(:whatever).should be_nil end diff --git a/spec/unit/ssl/key_spec.rb b/spec/unit/ssl/key_spec.rb index 112505087..4cea5491c 100755 --- a/spec/unit/ssl/key_spec.rb +++ b/spec/unit/ssl/key_spec.rb @@ -71,7 +71,7 @@ describe Puppet::SSL::Key do end it "should not try to use the provided password file if the file does not exist" do - FileTest.stubs(:exist?).returns false + Puppet::FileSystem::File.stubs(:exist?).returns false @key.password_file = "/path/to/password" path = "/my/path" @@ -84,7 +84,7 @@ describe Puppet::SSL::Key do end it "should read the key with the password retrieved from the password file if one is provided" do - FileTest.stubs(:exist?).returns true + Puppet::FileSystem::File.stubs(:exist?).returns true @key.password_file = "/path/to/password" path = "/my/path" @@ -154,7 +154,7 @@ describe Puppet::SSL::Key do describe "with a password file set" do it "should return a nil password if the password file does not exist" do - FileTest.expects(:exist?).with("/path/to/pass").returns false + Puppet::FileSystem::File.expects(:exist?).with("/path/to/pass").returns false File.expects(:read).with("/path/to/pass").never @instance.password_file = "/path/to/pass" @@ -163,7 +163,7 @@ describe Puppet::SSL::Key do end it "should return the contents of the password file as its password" do - FileTest.expects(:exist?).with("/path/to/pass").returns true + Puppet::FileSystem::File.expects(:exist?).with("/path/to/pass").returns true File.expects(:read).with("/path/to/pass").returns "my password" @instance.password_file = "/path/to/pass" diff --git a/spec/unit/ssl/oids_spec.rb b/spec/unit/ssl/oids_spec.rb new file mode 100644 index 000000000..ee95bb942 --- /dev/null +++ b/spec/unit/ssl/oids_spec.rb @@ -0,0 +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 + 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 + 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 + 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 + end + + it "returns false when an invalid OID shortname is passed" do + described_class.subtree_of?('IANA', 'bananas').should be_false + end + end +end diff --git a/spec/unit/ssl/validator_spec.rb b/spec/unit/ssl/validator_spec.rb index fe0904cb8..2b8cfb0f9 100644 --- a/spec/unit/ssl/validator_spec.rb +++ b/spec/unit/ssl/validator_spec.rb @@ -1,8 +1,8 @@ require 'spec_helper' -require 'puppet/ssl/validator' +require 'puppet/ssl' require 'puppet/ssl/configuration' -describe Puppet::SSL::Validator do +describe Puppet::SSL::Validator::DefaultValidator do let(:ssl_context) do mock('OpenSSL::X509::StoreContext') end @@ -14,8 +14,16 @@ describe Puppet::SSL::Validator do :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_configuration) + described_class.new(ssl_configuration, + ssl_host) end before :each do @@ -49,17 +57,20 @@ describe Puppet::SSL::Validator 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 end end + it 'is true for the SSL certificate ending the chain' do (cert_chain.length - 1).times do subject.call(true, ssl_context) @@ -67,17 +78,20 @@ describe Puppet::SSL::Validator do subject.call(true, ssl_context).should be_true 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 end end + it 'is false for the SSL certificate ending the chain' do (cert_chain.length - 1).times do subject.call(true, ssl_context) @@ -85,13 +99,16 @@ describe Puppet::SSL::Validator do subject.call(true, ssl_context).should be_false 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 end + it 'makes the error available through #verify_errors' do subject.call(true, ssl_context) subject.verify_errors.should == ["BOOM!"] @@ -100,11 +117,28 @@ describe Puppet::SSL::Validator do end end - describe '#register_verify_callback' do - it 'registers itself using #verify_callback' do + 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) - subject.register_verify_callback(connection) + 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 @@ -120,17 +154,21 @@ describe Puppet::SSL::Validator do before :each do subject.stubs(:has_authz_peer_cert).returns(true) end + it 'is true' do subject.valid_peer?.should be_true 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 end + it 'makes a helpful error message available via #verify_errors' do subject.valid_peer? subject.verify_errors.should == [expected_authz_error_msg] @@ -143,22 +181,27 @@ describe Puppet::SSL::Validator 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 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 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 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 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 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 end diff --git a/spec/unit/status_spec.rb b/spec/unit/status_spec.rb index 3f6ae7d82..8f598f579 100755 --- a/spec/unit/status_spec.rb +++ b/spec/unit/status_spec.rb @@ -37,4 +37,13 @@ describe Puppet::Status do new_status = Puppet::Status.convert_from('yaml', status.render('yaml')) new_status.should equal_attributes_of(status) end + + it "serializes to PSON that conforms to the status schema", :unless => Puppet.features.microsoft_windows? do + schema = JSON.parse(File.read('api/schemas/status.json')) + status = Puppet::Status.new + status.version = Puppet.version + + JSON::Validator.validate!(JSON_META_SCHEMA, schema) + JSON::Validator.validate!(schema, status.render('pson')) + end end diff --git a/spec/unit/transaction/event_spec.rb b/spec/unit/transaction/event_spec.rb index a60e6e907..8e62e02f6 100755 --- a/spec/unit/transaction/event_spec.rb +++ b/spec/unit/transaction/event_spec.rb @@ -15,14 +15,6 @@ end describe Puppet::Transaction::Event do include PuppetSpec::Files - [:previous_value, :desired_value, :property, :name, :message, :file, :line, :tags, :audited].each do |attr| - it "should support #{attr}" do - event = Puppet::Transaction::Event.new - event.send(attr.to_s + "=", "foo") - event.send(attr).should == "foo" - end - end - it "should support resource" do event = Puppet::Transaction::Event.new event.resource = TestResource.new @@ -101,7 +93,7 @@ describe Puppet::Transaction::Event do end it "should set the tags to the event tags" do - Puppet::Util::Log.expects(:new).with { |args| args[:tags] == %w{one two} } + Puppet::Util::Log.expects(:new).with { |args| args[:tags].to_a.should =~ %w{one two} } Puppet::Transaction::Event.new(:tags => %w{one two}).send_log end diff --git a/spec/unit/transaction/report_spec.rb b/spec/unit/transaction/report_spec.rb index 7195b0623..661349dab 100755 --- a/spec/unit/transaction/report_spec.rb +++ b/spec/unit/transaction/report_spec.rb @@ -4,6 +4,19 @@ require 'spec_helper' require 'puppet' require 'puppet/transaction/report' +# the json-schema gem doesn't support windows +if not Puppet.features.microsoft_windows? + REPORT_SCHEMA_URI = File.join(File.dirname(__FILE__), '../../../api/schemas/report.json') + REPORT_SCHEMA = JSON.parse(File.read(REPORT_SCHEMA_URI)) + + describe "report schema" do + it "should validate against the json meta-schema" do + JSON::Validator.validate!(JSON_META_SCHEMA, REPORT_SCHEMA) + end + end + +end + describe Puppet::Transaction::Report do include PuppetSpec::Files before do @@ -392,6 +405,12 @@ describe Puppet::Transaction::Report do expect_equivalent_reports(tripped, report) end + it "generates pson which validates against the report schema", :unless => Puppet.features.microsoft_windows? do + Puppet[:report_serialization_format] = "pson" + report = generate_report + JSON::Validator.validate!(REPORT_SCHEMA, report.render) + end + it "can make a round trip through yaml" do Puppet[:report_serialization_format] = "yaml" report = generate_report @@ -458,7 +477,7 @@ describe Puppet::Transaction::Report do status = Puppet::Resource::Status.new(Puppet::Type.type(:notify).new(:title => "a resource")) status.changed = true - report = Puppet::Transaction::Report.new('testy', 1357986, 'test_environment', "df34516e-4050-402d-a166-05b03b940749") + 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) diff --git a/spec/unit/transaction/resource_harness_spec.rb b/spec/unit/transaction/resource_harness_spec.rb index 0c250ae92..afe1ec912 100755 --- a/spec/unit/transaction/resource_harness_spec.rb +++ b/spec/unit/transaction/resource_harness_spec.rb @@ -127,6 +127,34 @@ describe Puppet::Transaction::ResourceHarness do false end end + + newproperty(:brillig) do + desc "A property that raises a StandardError exception when you test if it's insync?" + def sync + end + + def retrieve + :absent + end + + def insync?(reference_value) + raise ZeroDivisionError.new('brillig') + end + end + + newproperty(:slithy) do + desc "A property that raises an Exception when you test if it's insync?" + def sync + end + + def retrieve + :absent + end + + def insync?(reference_value) + raise Exception.new('slithy') + end + end end stubProvider end @@ -164,6 +192,35 @@ describe Puppet::Transaction::ResourceHarness do end end + describe "when a StandardError exception occurs during insync?" do + before :each do + stub_provider = make_stub_provider + @resource = stub_provider.new :name => 'name', :brillig => 1 + @resource.expects(:err).never + end + + it "should record a failure event" do + @status = @harness.evaluate(@resource) + @status.events[0].name.to_s.should == 'brillig_changed' + @status.events[0].property.should == 'brillig' + @status.events[0].status.should == 'failure' + end + end + + describe "when an Exception occurs during insync?" do + before :each do + stub_provider = make_stub_provider + @resource = stub_provider.new :name => 'name', :slithy => 1 + @resource.expects(:err).never + end + + it "should log and pass the exception through" do + lambda { @harness.evaluate(@resource) }.should raise_error(Exception, /slithy/) + @logs.first.message.should == "change from absent to 1 failed: slithy" + @logs.first.level.should == :err + end + end + describe "when auditing" do it "should not call insync? on parameters that are merely audited" do stub_provider = make_stub_provider @@ -180,7 +237,9 @@ describe Puppet::Transaction::ResourceHarness do File.open(test_file, 'w').close resource = Puppet::Type.type(:file).new :path => test_file, :audit => ['group'], :backup => false resource.expects(:err).never # make sure no exceptions get swallowed + status = @harness.evaluate(resource) + status.events.each do |event| event.status.should != 'failure' end @@ -188,222 +247,13 @@ describe Puppet::Transaction::ResourceHarness do end describe "when applying changes" do - [false, true].each do |noop_mode|; describe (noop_mode ? "in noop mode" : "in normal mode") do - [nil, @mode_750].each do |machine_state|; describe (machine_state ? "with a file initially present" : "with no file initially present") do - [nil, @mode_750, @mode_755].each do |yaml_mode| - [nil, :file, :absent].each do |yaml_ensure|; describe "with mode=#{yaml_mode.inspect} and ensure=#{yaml_ensure.inspect} stored in state.yml" do - [false, true].each do |auditing_ensure| - [false, true].each do |auditing_mode| - auditing = [] - auditing.push(:mode) if auditing_mode - auditing.push(:ensure) if auditing_ensure - [nil, :file, :absent].each do |ensure_property| # what we set "ensure" to in the manifest - [nil, @mode_750, @mode_755].each do |mode_property| # what we set "mode" to in the manifest - manifest_settings = {} - manifest_settings[:audit] = auditing if !auditing.empty? - manifest_settings[:ensure] = ensure_property if ensure_property - manifest_settings[:mode] = mode_property if mode_property - describe "with manifest settings #{manifest_settings.inspect}" do; it "should behave properly" do - # Set up preconditions - test_file = tmpfile('foo') - if machine_state - File.open(test_file, 'w', machine_state.to_i(8)).close - end - - Puppet[:noop] = noop_mode - params = { :path => test_file, :backup => false } - params.merge!(manifest_settings) - resource = Puppet::Type.type(:file).new params - - @harness.cache(resource, :mode, yaml_mode) if yaml_mode - @harness.cache(resource, :ensure, yaml_ensure) if yaml_ensure - - fake_time = Time.utc(2011, 'jan', 3, 12, 24, 0) - Time.stubs(:now).returns(fake_time) # So that Puppet::Resource::Status objects will compare properly - - resource.expects(:err).never # make sure no exceptions get swallowed - status = @harness.evaluate(resource) # do the thing - - # check that the state of the machine has been properly updated - expected_logs = [] - expected_status_events = [] - if auditing_mode - @harness.cached(resource, :mode).should == (machine_state || :absent) - else - @harness.cached(resource, :mode).should == yaml_mode - end - if auditing_ensure - @harness.cached(resource, :ensure).should == (machine_state ? :file : :absent) - else - @harness.cached(resource, :ensure).should == yaml_ensure - end - if ensure_property == :file - file_would_be_there_if_not_noop = true - elsif ensure_property == nil - file_would_be_there_if_not_noop = machine_state != nil - else # ensure_property == :absent - file_would_be_there_if_not_noop = false - end - file_should_be_there = noop_mode ? machine_state != nil : file_would_be_there_if_not_noop - File.exists?(test_file).should == file_should_be_there - if file_should_be_there - if noop_mode - expected_file_mode = machine_state - else - expected_file_mode = mode_property || machine_state - end - if !expected_file_mode - # we didn't specify a mode and the file was created, so mode comes from umode - else - file_mode = File.stat(test_file).mode & 0777 - file_mode.should == expected_file_mode.to_i(8) - end - end - - # Test log output for the "mode" parameter - previously_recorded_mode_already_logged = false - mode_status_msg = nil - if machine_state && file_would_be_there_if_not_noop && mode_property && machine_state != mode_property - if noop_mode - what_happened = "current_value #{machine_state}, should be #{mode_property} (noop)" - expected_status = 'noop' - else - what_happened = "mode changed '#{machine_state}' to '#{mode_property}'" - expected_status = 'success' - end - if auditing_mode && yaml_mode && yaml_mode != machine_state - previously_recorded_mode_already_logged = true - mode_status_msg = "#{what_happened} (previously recorded value was #{yaml_mode})" - else - mode_status_msg = what_happened - end - expected_logs << "notice: /#{resource}/mode: #{mode_status_msg}" - end - if @harness.cached(resource, :mode) && @harness.cached(resource, :mode) != yaml_mode - if yaml_mode - unless previously_recorded_mode_already_logged - mode_status_msg = "audit change: previously recorded value #{yaml_mode} has been changed to #{@harness.cached(resource, :mode)}" - expected_logs << "notice: /#{resource}/mode: #{mode_status_msg}" - expected_status = 'audit' - end - else - expected_logs << "notice: /#{resource}/mode: audit change: newly-recorded value #{@harness.cached(resource, :mode)}" - end - end - if mode_status_msg - expected_status_events << Puppet::Transaction::Event.new( - :source_description => "/#{resource}/mode", :resource => resource, :file => nil, - :line => nil, :tags => %w{file}, :desired_value => mode_property, - :historical_value => yaml_mode, :message => mode_status_msg, :name => :mode_changed, - :previous_value => machine_state || :absent, :property => :mode, :status => expected_status, - :audited => auditing_mode) - end - - # Test log output for the "ensure" parameter - previously_recorded_ensure_already_logged = false - ensure_status_msg = nil - if file_would_be_there_if_not_noop != (machine_state != nil) - if noop_mode - what_happened = "current_value #{machine_state ? 'file' : 'absent'}, should be #{file_would_be_there_if_not_noop ? 'file' : 'absent'} (noop)" - expected_status = 'noop' - else - what_happened = file_would_be_there_if_not_noop ? 'created' : 'removed' - expected_status = 'success' - end - if auditing_ensure && yaml_ensure && yaml_ensure != (machine_state ? :file : :absent) - previously_recorded_ensure_already_logged = true - ensure_status_msg = "#{what_happened} (previously recorded value was #{yaml_ensure})" - else - ensure_status_msg = "#{what_happened}" - end - expected_logs << "notice: /#{resource}/ensure: #{ensure_status_msg}" - end - if @harness.cached(resource, :ensure) && @harness.cached(resource, :ensure) != yaml_ensure - if yaml_ensure - unless previously_recorded_ensure_already_logged - ensure_status_msg = "audit change: previously recorded value #{yaml_ensure} has been changed to #{@harness.cached(resource, :ensure)}" - expected_logs << "notice: /#{resource}/ensure: #{ensure_status_msg}" - expected_status = 'audit' - end - else - expected_logs << "notice: /#{resource}/ensure: audit change: newly-recorded value #{@harness.cached(resource, :ensure)}" - end - end - if ensure_status_msg - if ensure_property == :file - ensure_event_name = :file_created - elsif ensure_property == nil - ensure_event_name = :file_changed - else # ensure_property == :absent - ensure_event_name = :file_removed - end - expected_status_events << Puppet::Transaction::Event.new( - :source_description => "/#{resource}/ensure", :resource => resource, :file => nil, - :line => nil, :tags => %w{file}, :desired_value => ensure_property, - :historical_value => yaml_ensure, :message => ensure_status_msg, :name => ensure_event_name, - :previous_value => machine_state ? :file : :absent, :property => :ensure, - :status => expected_status, :audited => auditing_ensure) - end - - # Actually check the logs. - @logs.map {|l| "#{l.level}: #{l.source}: #{l.message}"}.should =~ expected_logs - - # All the log messages should show up as events except the "newly-recorded" ones. - expected_event_logs = @logs.reject {|l| l.message =~ /newly-recorded/ } - status.events.map {|e| e.message}.should =~ expected_event_logs.map {|l| l.message } - events_to_hash(status.events).should =~ events_to_hash(expected_status_events) - - # Check change count - this is the number of changes that actually occurred. - expected_change_count = 0 - if (machine_state != nil) != file_should_be_there - expected_change_count = 1 - elsif machine_state != nil - if expected_file_mode != machine_state - expected_change_count = 1 - end - end - status.change_count.should == expected_change_count - - # Check out of sync count - this is the number - # of changes that would have occurred in - # non-noop mode. - expected_out_of_sync_count = 0 - if (machine_state != nil) != file_would_be_there_if_not_noop - expected_out_of_sync_count = 1 - elsif machine_state != nil - if mode_property != nil && mode_property != machine_state - expected_out_of_sync_count = 1 - end - end - if !noop_mode - expected_out_of_sync_count.should == expected_change_count - end - status.out_of_sync_count.should == expected_out_of_sync_count - - # Check legacy summary fields - status.changed.should == (expected_change_count != 0) - status.out_of_sync.should == (expected_out_of_sync_count != 0) - - # Check the :synced field on state.yml - synced_should_be_set = !noop_mode && status.changed - (@harness.cached(resource, :synced) != nil).should == synced_should_be_set - end; end - end - end - end - end - end; end - end - end; end - end; end - it "should not apply changes if allow_changes?() returns false" do test_file = tmpfile('foo') resource = Puppet::Type.type(:file).new :path => test_file, :backup => false, :ensure => :file resource.expects(:err).never # make sure no exceptions get swallowed @harness.expects(:allow_changes?).with(resource).returns false status = @harness.evaluate(resource) - File.exists?(test_file).should == false + Puppet::FileSystem::File.exist?(test_file).should == false end end diff --git a/spec/unit/transaction_spec.rb b/spec/unit/transaction_spec.rb index 840b34c15..04ce08c4f 100755 --- a/spec/unit/transaction_spec.rb +++ b/spec/unit/transaction_spec.rb @@ -308,7 +308,7 @@ describe Puppet::Transaction do transaction.evaluate generated.each do |res| - res.must be_tagged(generator.tags) + res.must be_tagged(*generator.tags) end end end @@ -404,7 +404,6 @@ describe Puppet::Transaction do it "should otherwise let the resource determine if it is missing tags" do tags = ['one', 'two'] @transaction.tags = tags - @resource.expects(:tagged?).with(*tags).returns(false) @transaction.should be_missing_tags(@resource) end end @@ -478,6 +477,48 @@ describe Puppet::Transaction do end end + describe "during teardown" do + before :each do + @catalog = Puppet::Resource::Catalog.new + @transaction = Puppet::Transaction.new(@catalog, nil, Puppet::Graph::RandomPrioritizer.new) + end + + it "should call ::post_resource_eval on provider classes that support it" do + @resource = Puppet::Type.type(:notify).new :title => "foo" + @catalog.add_resource @resource + + # 'expects' will cause 'respond_to?(:post_resource_eval)' to return true + @resource.provider.class.expects(:post_resource_eval) + @transaction.evaluate + end + + it "should call ::post_resource_eval even if other providers' ::post_resource_eval fails" do + @resource3 = Puppet::Type.type(:user).new :title => "bloo" + @resource3.provider.class.stubs(:post_resource_eval).raises + @resource4 = Puppet::Type.type(:notify).new :title => "blob" + @resource4.provider.class.stubs(:post_resource_eval).raises + @catalog.add_resource @resource3 + @catalog.add_resource @resource4 + + # ruby's Set does not guarantee ordering, so both resource3 and resource4 + # need to expect post_resource_eval, rather than just the 'first' one. + @resource3.provider.class.expects(:post_resource_eval) + @resource4.provider.class.expects(:post_resource_eval) + + @transaction.evaluate + end + + it "should call ::post_resource_eval even if one of the resources fails" do + @resource3 = Puppet::Type.type(:notify).new :title => "bloo" + @resource3.stubs(:retrieve_resource).raises + @catalog.add_resource @resource3 + + @resource3.provider.class.expects(:post_resource_eval) + + @transaction.evaluate + end + end + describe 'when checking application run state' do before do @catalog = Puppet::Resource::Catalog.new @@ -594,32 +635,37 @@ describe Puppet::Transaction, " when determining tags" do it "should default to the tags specified in the :tags setting" do Puppet[:tags] = "one" - @transaction.tags.should == %w{one} + @transaction.should be_tagged("one") end it "should split tags based on ','" do Puppet[:tags] = "one,two" - @transaction.tags.should == %w{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.tags.should == %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.tags.should == %w{one::two} + @transaction.should be_tagged("one::two") end it "should accept a comma-delimited string" do @transaction.tags = "one, two" - @transaction.tags.should == %w{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.tags.should == [] + @transaction.should_not be_tagged("one") end end diff --git a/spec/unit/type/component_spec.rb b/spec/unit/type/component_spec.rb index 82c822dea..f3326a161 100755 --- a/spec/unit/type/component_spec.rb +++ b/spec/unit/type/component_spec.rb @@ -42,8 +42,8 @@ describe component do component.new(:name => "Class[foo]").pathbuilder.must == ["Foo"] end - it "should produce an empty string if the component models the 'main' class" do - component.new(:name => "Class[main]").pathbuilder.must == [""] + it "should produce the class name even for the class named main" do + component.new(:name => "Class[main]").pathbuilder.must == ["Main"] end it "should produce a resource reference if the component does not model a class" do diff --git a/spec/unit/type/exec_spec.rb b/spec/unit/type/exec_spec.rb index 85a5809dc..6d780b3ff 100755 --- a/spec/unit/type/exec_spec.rb +++ b/spec/unit/type/exec_spec.rb @@ -8,6 +8,8 @@ describe Puppet::Type.type(:exec) do Puppet.features.stubs(:root?).returns(true) output = rest.delete(:output) || '' + + output = Puppet::Util::Execution::ProcessOutput.new(output, exitstatus) tries = rest[:tries] || 1 args = { @@ -21,14 +23,12 @@ describe Puppet::Type.type(:exec) do exec = Puppet::Type.type(:exec).new(args) status = stub "process", :exitstatus => exitstatus - Puppet::Util::SUIDManager.expects(:run_and_capture).times(tries). + Puppet::Util::Execution.expects(:execute).times(tries). with() { |*args| args[0] == command && - args[1] == nil && - args[2] == nil && - args[3][:override_locale] == false && - args[3].has_key?(:custom_environment) - } .returns([output, status]) + args[1][:override_locale] == false && + args[1].has_key?(:custom_environment) + }.returns(output) return exec end @@ -61,7 +61,7 @@ describe Puppet::Type.type(:exec) do end describe "when execing" do - it "should use the 'run_and_capture' method to exec" do + it "should use the 'execute' method to exec" do exec_tester("true").refresh.should == :executed_command end @@ -753,4 +753,11 @@ describe Puppet::Type.type(:exec) do type.new(:command => abs, :path => path).must be end end + describe "when providing a umask" do + it "should fail if an invalid umask is used" do + resource = Puppet::Type.type(:exec).new :command => @command + expect { resource[:umask] = '0028'}.to raise_error(Puppet::ResourceError, /umask specification is invalid/) + expect { resource[:umask] = '28' }.to raise_error(Puppet::ResourceError, /umask specification is invalid/) + end + end end diff --git a/spec/unit/type/file/content_spec.rb b/spec/unit/type/file/content_spec.rb index c1c97a452..5a73dceb1 100755 --- a/spec/unit/type/file/content_spec.rb +++ b/spec/unit/type/file/content_spec.rb @@ -84,6 +84,13 @@ describe content do @content.should.must == string end + + it "should convert the value to ASCII-8BIT", :if => "".respond_to?(:encode) do + @content = content.new(:resource => @resource) + @content.should= "Let's make a \u{2603}" + + @content.actual_content.should == "Let's make a \xE2\x98\x83".force_encoding(Encoding::ASCII_8BIT) + end end describe "when retrieving the current content" do @@ -329,8 +336,10 @@ describe content do end it "should copy content from the source to the file" do + dest_file = Puppet::FileSystem::File.new(@filename) @resource.write(@source) - IO.binread(@filename).should == @source_content + + dest_file.binread.should == @source_content end it "should return the checksum computed" do @@ -358,8 +367,10 @@ describe content do end it "should write the contents to the file" do + dest_file = Puppet::FileSystem::File.new(@filename) @resource.write(@source) - IO.binread(@filename).should == @source_content + + dest_file.binread.should == @source_content end it "should not write anything if source is not found" do diff --git a/spec/unit/type/file/ctime_spec.rb b/spec/unit/type/file/ctime_spec.rb index ba46da286..eea0f1f92 100755 --- a/spec/unit/type/file/ctime_spec.rb +++ b/spec/unit/type/file/ctime_spec.rb @@ -16,7 +16,7 @@ describe Puppet::Type.type(:file).attrclass(:ctime) do @resource[:audit] = [:ctime] # this .to_resource audit behavior is magical :-( - @resource.to_resource[:ctime].should == File.stat(@filename).ctime + @resource.to_resource[:ctime].should == Puppet::FileSystem::File.new(@filename).stat.ctime end it "should return absent if auditing an absent file" do diff --git a/spec/unit/type/file/mode_spec.rb b/spec/unit/type/file/mode_spec.rb index dc43ae498..8663fe57d 100755 --- a/spec/unit/type/file/mode_spec.rb +++ b/spec/unit/type/file/mode_spec.rb @@ -18,6 +18,10 @@ describe Puppet::Type.type(:file).attrclass(:mode) do expect { mode.value = '0755' }.not_to raise_error end + it "should accept valid symbolic strings" do + expect { mode.value = 'g+w,u-x' }.not_to raise_error + end + it "should not accept strings other than octal numbers" do expect do mode.value = 'readable please!' @@ -35,6 +39,10 @@ describe Puppet::Type.type(:file).attrclass(:mode) do mode.munge('0644').should == '644' end + it "should accept symbolic strings as arguments and return them intact" do + mode.munge('u=rw,go=r').should == 'u=rw,go=r' + end + it "should accept integers are arguments" do mode.munge(0644).should == '644' end @@ -72,11 +80,34 @@ describe Puppet::Type.type(:file).attrclass(:mode) do mode.must_not be_insync('755') end - it "should return true if the file is a link and we are managing links", :unless => Puppet.features.microsoft_windows? do - File.symlink('anything', path) + it "should return true if the file is a link and we are managing links", :if => Puppet.features.manages_symlinks? do + Puppet::FileSystem::File.new('anything').symlink(path) mode.must be_insync('644') end + + describe "with a symbolic mode" do + let(:resource_sym) { Puppet::Type.type(:file).new :path => path, :mode => 'u+w,g-w' } + let(:mode_sym) { resource_sym.property(:mode) } + + it "should return true if the mode matches, regardless of other bits" do + FileUtils.touch(path) + + mode_sym.must be_insync('644') + end + + it "should return false if the mode requires 0's where there are 1's" do + FileUtils.touch(path) + + mode_sym.must_not be_insync('624') + end + + it "should return false if the mode requires 1's where there are 0's" do + FileUtils.touch(path) + + mode_sym.must_not be_insync('044') + end + end end describe "#retrieve" do @@ -145,4 +176,19 @@ describe Puppet::Type.type(:file).attrclass(:mode) do end end end + + describe "#sync with a symbolic mode" do + let(:resource_sym) { Puppet::Type.type(:file).new :path => path, :mode => 'u+w,g-w' } + let(:mode_sym) { resource_sym.property(:mode) } + + before { FileUtils.touch(path) } + + it "changes only the requested bits" do + # lower nibble must be set to 4 for the sake of passing on Windows + FileUtils.chmod 0464, path + mode_sym.sync + file = Puppet::FileSystem::File.new(path) + (file.stat.mode & 0777).to_s(8).should == "644" + end + end end diff --git a/spec/unit/type/file/mtime_spec.rb b/spec/unit/type/file/mtime_spec.rb index 93abcb2fe..a20bdf196 100755 --- a/spec/unit/type/file/mtime_spec.rb +++ b/spec/unit/type/file/mtime_spec.rb @@ -16,7 +16,7 @@ describe Puppet::Type.type(:file).attrclass(:mtime) do @resource[:audit] = [:mtime] # this .to_resource audit behavior is magical :-( - @resource.to_resource[:mtime].should == File.stat(@filename).mtime + @resource.to_resource[:mtime].should == Puppet::FileSystem::File.new(@filename).stat.mtime end it "should return absent if auditing an absent file" do diff --git a/spec/unit/type/file/source_spec.rb b/spec/unit/type/file/source_spec.rb index 36cad8849..e645842c0 100755 --- a/spec/unit/type/file/source_spec.rb +++ b/spec/unit/type/file/source_spec.rb @@ -154,11 +154,10 @@ describe Puppet::Type.type(:file).attrclass(:source) do describe "when copying the source values" do before do - @resource = Puppet::Type.type(:file).new :path => @foobar @source = source.new(:resource => @resource) - @metadata = stub 'metadata', :owner => 100, :group => 200, :mode => 123, :checksum => "{md5}asdfasdf", :ftype => "file", :source => @foobar + @metadata = stub 'metadata', :owner => 100, :group => 200, :mode => "173", :checksum => "{md5}asdfasdf", :ftype => "file", :source => @foobar @source.stubs(:metadata).returns @metadata Puppet.features.stubs(:root?).returns true @@ -202,7 +201,7 @@ describe Puppet::Type.type(:file).attrclass(:source) do @resource[:content].must == @metadata.checksum end - it "should not copy the metadata's owner to the resource if it is already set" do + it "should not copy the metadata's owner, group, checksum and mode to the resource if they are already set" do @resource[:owner] = 1 @resource[:group] = 2 @resource[:mode] = 3 @@ -217,26 +216,153 @@ describe Puppet::Type.type(:file).attrclass(:source) do end describe "and puppet is not running as root" do - it "should not try to set the owner" do - Puppet.features.expects(:root?).returns false + before do + Puppet.features.stubs(:root?).returns false + end + it "should not try to set the owner" do @source.copy_source_values @resource[:owner].should be_nil end + + it "should not try to set the group" do + @source.copy_source_values + @resource[:group].should be_nil + end + end + + context "when source_permissions is `use_when_creating`" do + before :each do + @resource[:source_permissions] = "use_when_creating" + Puppet.features.expects(:root?).returns true + @source.stubs(:local?).returns(false) + end + + context "when managing a new file" do + it "should copy owner and group from local sources" do + @source.stubs(:local?).returns true + + @source.copy_source_values + + @resource[:owner].must == 100 + @resource[:group].must == 200 + @resource[:mode].must == "173" + end + + it "copies the remote owner" do + @source.copy_source_values + + @resource[:owner].must == 100 + end + + it "copies the remote group" do + @source.copy_source_values + + @resource[:group].must == 200 + end + + it "copies the remote mode" do + @source.copy_source_values + + @resource[:mode].must == "173" + end + end + + context "when managing an existing file" do + before :each do + Puppet::FileSystem::File.stubs(:exist?).with(@resource[:path]).returns(true) + end + + it "should not copy owner, group or mode from local sources" do + @source.stubs(:local?).returns true + + @source.copy_source_values + + @resource[:owner].must be_nil + @resource[:group].must be_nil + @resource[:mode].must be_nil + end + + it "preserves the local owner" do + @source.copy_source_values + + @resource[:owner].must be_nil + end + + it "preserves the local group" do + @source.copy_source_values + + @resource[:group].must be_nil + end + + it "preserves the local mode" do + @source.copy_source_values + + @resource[:mode].must be_nil + end + end + end + + context "when source_permissions is `ignore`" do + before :each do + @resource[:source_permissions] = "ignore" + @source.stubs(:local?).returns(false) + Puppet.features.expects(:root?).returns true + end + + it "should not copy owner, group or mode from local sources" do + @source.stubs(:local?).returns true + + @source.copy_source_values + + @resource[:owner].must be_nil + @resource[:group].must be_nil + @resource[:mode].must be_nil + end + + it "preserves the local owner" do + @source.copy_source_values + + @resource[:owner].must be_nil + end + + it "preserves the local group" do + @source.copy_source_values + + @resource[:group].must be_nil + end + + it "preserves the local mode" do + @source.copy_source_values + + @resource[:mode].must be_nil + end end describe "on Windows" do before :each do Puppet.features.stubs(:microsoft_windows?).returns true end + let(:deprecation_message) { "Copying owner/mode/group from the" << + " source file on Windows is deprecated;" << + " use source_permissions => ignore." } - it "should not copy owner and group from remote sources" do + it "should copy only mode from remote sources" do @source.stubs(:local?).returns false @source.copy_source_values @resource[:owner].must be_nil @resource[:group].must be_nil + @resource[:mode].must == "173" + end + + it "should copy mode from remote sources" do + @source.stubs(:local?).returns false + + @source.copy_source_values + + @resource[:mode].must == "173" end it "should copy owner and group from local sources" do @@ -246,6 +372,51 @@ describe Puppet::Type.type(:file).attrclass(:source) do @resource[:owner].must == 100 @resource[:group].must == 200 + @resource[:mode].must == "173" + end + + it "should issue deprecation warning when copying metadata from remote sources when group, owner, and mode are unspecified" do + @source.stubs(:local?).returns false + Puppet.expects(:deprecation_warning).with(deprecation_message).at_least_once + + @source.copy_source_values + end + + it "should issue deprecation warning when copying metadata from remote sources if only user is unspecified" do + @source.stubs(:local?).returns false + Puppet.expects(:deprecation_warning).with(deprecation_message).at_least_once + @resource[:group] = 2 + @resource[:mode] = 3 + + @source.copy_source_values + end + + it "should issue deprecation warning when copying metadata from remote sources if only group is unspecified" do + @source.stubs(:local?).returns false + Puppet.expects(:deprecation_warning).with(deprecation_message).at_least_once + @resource[:owner] = 1 + @resource[:mode] = 3 + + @source.copy_source_values + end + + it "should issue deprecation warning when copying metadata from remote sources if only mode is unspecified" do + @source.stubs(:local?).returns false + Puppet.expects(:deprecation_warning).with(deprecation_message).at_least_once + @resource[:owner] = 1 + @resource[:group] = 2 + + @source.copy_source_values + end + + it "should not issue deprecation warning when copying metadata from remote sources if group, owner, and mode are all specified" do + @source.stubs(:local?).returns false + Puppet.expects(:deprecation_warning).with(deprecation_message).never + @resource[:owner] = 1 + @resource[:group] = 2 + @resource[:mode] = 3 + + @source.copy_source_values end end end @@ -358,5 +529,4 @@ describe Puppet::Type.type(:file).attrclass(:source) do end end end - end diff --git a/spec/unit/type/file_spec.rb b/spec/unit/type/file_spec.rb index dd695eeda..75b58926a 100755 --- a/spec/unit/type/file_spec.rb +++ b/spec/unit/type/file_spec.rb @@ -10,7 +10,6 @@ describe Puppet::Type.type(:file) do let(:catalog) { Puppet::Resource::Catalog.new } before do - @real_posix = Puppet.features.posix? Puppet.features.stubs("posix?").returns(true) end @@ -78,33 +77,29 @@ describe Puppet::Type.type(:file) do end describe "when using UNC filenames", :if => Puppet.features.microsoft_windows? do - before :each do - pending("UNC file paths not yet supported") - end - it "should remove trailing slashes" do - file[:path] = "//server/foo/bar/baz/" - file[:path].should == "//server/foo/bar/baz" + file[:path] = "//localhost/foo/bar/baz/" + file[:path].should == "//localhost/foo/bar/baz" end it "should remove double slashes" do - file[:path] = "//server/foo/bar//baz" - file[:path].should == "//server/foo/bar/baz" + file[:path] = "//localhost/foo/bar//baz" + file[:path].should == "//localhost/foo/bar/baz" end it "should remove trailing double slashes" do - file[:path] = "//server/foo/bar/baz//" - file[:path].should == "//server/foo/bar/baz" + 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] = "//server/foo/" - file[:path].should == "//server/foo" + file[:path] = "//localhost/foo/" + file[:path].should == "//localhost/foo" end it "should not modify a sharename" do - file[:path] = "//server/foo" - file[:path].should == "//server/foo" + file[:path] = "//localhost/foo" + file[:path].should == "//localhost/foo" end end end @@ -352,7 +347,7 @@ describe Puppet::Type.type(:file) do file[:ensure].should == :file end - it "should set a desired 'ensure' value if none is set and 'target' is set" do + 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 @@ -397,7 +392,7 @@ describe Puppet::Type.type(:file) do :target => "some_target", :source => File.expand_path("some_source"), }.each do |param, value| - it "should omit the #{param} parameter" do + 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) @@ -603,7 +598,7 @@ describe Puppet::Type.type(:file) do file.recurse_link("first" => @resource) end - it "should set the target to the full path of discovered file and set :ensure to :link if the file is not a directory" do + 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) @@ -925,20 +920,20 @@ describe Puppet::Type.type(:file) do file.remove_existing(:directory).should == true - File.exists?(file[:path]).should == false + Puppet::FileSystem::File.exist?(file[:path]).should == false end - it "should remove an existing link", :unless => Puppet.features.microsoft_windows? do + 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) - FileUtils.symlink(target, path) + Puppet::FileSystem::File.new(target).symlink(path) file[:target] = target file.remove_existing(:directory).should == true - File.exists?(file[:path]).should == false + Puppet::FileSystem::File.exist?(file[:path]).should == false end it "should fail if the file is not a file, link, or directory" do @@ -952,7 +947,7 @@ describe Puppet::Type.type(:file) do file.stat file.stubs(:stat).returns stub('stat', :ftype => 'file') - File.stubs(:unlink) + Puppet::FileSystem::File.stubs(:unlink) file.remove_existing(:directory).should == true file.instance_variable_get(:@stat).should == :needs_stat @@ -1010,11 +1005,11 @@ describe Puppet::Type.type(:file) do end end - describe "#stat", :unless => Puppet.features.microsoft_windows? do + describe "#stat", :if => described_class.defaultprovider.feature?(:manages_symlinks) do before do target = tmpfile('link_target') FileUtils.touch(target) - FileUtils.symlink(target, path) + Puppet::FileSystem::File.new(target).symlink(path) file[:target] = target file[:links] = :manage # so we always use :lstat @@ -1033,7 +1028,7 @@ describe Puppet::Type.type(:file) do end it "should return nil if the file does not exist" do - file[:path] = '/foo/bar/baz/non-existent' + file[:path] = make_absolute('/foo/bar/baz/non-existent') file.stat.should be_nil end @@ -1207,7 +1202,7 @@ describe Puppet::Type.type(:file) do describe "when autorequiring" do describe "target" do - it "should require file resource when specified with the target property" 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 @@ -1229,7 +1224,7 @@ describe Puppet::Type.type(:file) do reqs[0].target.must == link end - it "should not require target if target is not managed" do + 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 @@ -1272,8 +1267,8 @@ describe Puppet::Type.type(:file) do describe "on Windows systems", :if => Puppet.features.microsoft_windows? do describe "when using UNC filenames" do it "should autorequire its parent directory" do - file[:path] = '//server/foo/bar/baz' - dir = described_class.new(:path => "//server/foo/bar") + 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 @@ -1282,9 +1277,9 @@ describe Puppet::Type.type(:file) do end it "should autorequire its nearest ancestor directory" do - file = described_class.new(:path => "//server/foo/bar/baz/qux") - dir = described_class.new(:path => "//server/foo/bar/baz") - grandparent = described_class.new(:path => "//server/foo/bar") + 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 @@ -1295,13 +1290,13 @@ describe Puppet::Type.type(:file) do end it "should not autorequire anything when there is no nearest ancestor directory" do - file = described_class.new(:path => "//server/foo/bar/baz/qux") + 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 => "//server/foo") + file = described_class.new(:path => "//localhost/foo") catalog.add_resource file puts file.autorequire file.autorequire.should be_empty @@ -1311,49 +1306,46 @@ describe Puppet::Type.type(:file) do end end - describe "when managing links" do + describe "when managing links", :if => Puppet.features.manages_symlinks? do require 'tempfile' - if @real_posix - describe "on POSIX systems" do - before do - Dir.mkdir(path) - @target = File.join(path, "target") - @link = File.join(path, "link") - - File.open(@target, "w", 0644) { |f| f.puts "yayness" } - File.symlink(@target, @link) - - file[:path] = @link - file[:mode] = 0755 - - catalog.add_resource file - end + 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 - it "should default to managing the link" do - catalog.apply - # I convert them to strings so they display correctly if there's an error. - (File.stat(@target).mode & 007777).to_s(8).should == '644' - end + # to prevent the catalog from trying to write state.yaml + Puppet::Util::Storage.stubs(:store) + end - it "should be able to follow links" do - file[:links] = :follow - catalog.apply + it "should preserve the original file mode and ignore the one set by the link" do + @link_resource[:links] = :manage # default + catalog.apply - (File.stat(@target).mode & 007777).to_s(8).should == '755' - end - end - else # @real_posix - # should recode tests using expectations instead of using the filesystem + # I convert them to strings so they display correctly if there's an error. + (Puppet::FileSystem::File.new(@target).stat.mode & 007777).to_s(8).should == '644' end - describe "on Microsoft Windows systems" do - before do - Puppet.features.stubs(:posix?).returns(false) - Puppet.features.stubs(:microsoft_windows?).returns(true) - end + it "should 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 - it "should refuse to work with links" + (Puppet::FileSystem::File.new(@target).stat.mode & 007777).to_s(8).should == '755' + end end end @@ -1436,7 +1428,7 @@ describe Puppet::Type.type(:file) do catalog.apply - File.should be_exist(path) + Puppet::FileSystem::File.exist?(path).should be_true @logs.should_not be_any {|l| l.level != :notice } end end diff --git a/spec/unit/type/group_spec.rb b/spec/unit/type/group_spec.rb index f8d24f0df..55f6e548b 100755 --- a/spec/unit/type/group_spec.rb +++ b/spec/unit/type/group_spec.rb @@ -61,4 +61,24 @@ describe Puppet::Type.type(:group) do 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 + 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 2524d98c5..484ddf8e7 100755 --- a/spec/unit/type/k5login_spec.rb +++ b/spec/unit/type/k5login_spec.rb @@ -46,7 +46,7 @@ describe Puppet::Type.type(:k5login), :unless => Puppet.features.microsoft_windo it "should create the file when synced" do resource(:ensure => 'present').parameter(:ensure).sync - File.should be_exist path + Puppet::FileSystem::File.exist?(path).should be_true end end @@ -83,7 +83,7 @@ describe Puppet::Type.type(:k5login), :unless => Puppet.features.microsoft_windo it "should remove the file ensure is absent" do resource(:ensure => 'absent').property(:ensure).sync - File.should_not be_exist path + Puppet::FileSystem::File.exist?(path).should be_false end it "should write one principal to the file" do @@ -106,7 +106,7 @@ describe Puppet::Type.type(:k5login), :unless => Puppet.features.microsoft_windo it "should update the mode to #{mode}" do resource(:mode => mode).property(:mode).sync - (File.stat(path).mode & 07777).to_s(8).should == mode + (Puppet::FileSystem::File.new(path).stat.mode & 07777).to_s(8).should == mode end end end diff --git a/spec/unit/type/mount_spec.rb b/spec/unit/type/mount_spec.rb index 726577891..75b2f505d 100755 --- a/spec/unit/type/mount_spec.rb +++ b/spec/unit/type/mount_spec.rb @@ -536,4 +536,57 @@ describe Puppet::Type.type(:mount), :unless => Puppet.features.microsoft_windows run_in_catalog(resource) end end + + describe "establishing autorequires" do + + def create_resource(path) + described_class.new( + :name => path, + :provider => providerclass.new(path) + ) + end + + def create_catalog(*resources) + catalog = Puppet::Resource::Catalog.new + resources.each do |resource| + catalog.add_resource resource + end + + catalog + end + + let(:root_mount) { create_resource("/") } + let(:var_mount) { create_resource("/var") } + let(:log_mount) { create_resource("/var/log") } + + before do + create_catalog(root_mount, var_mount, log_mount) + end + + it "adds no autorequires for the root mount" do + expect(root_mount.autorequire).to be_empty + end + + it "adds the parent autorequire for a mount with one parent" do + parent_relationship = var_mount.autorequire[0] + + expect(var_mount.autorequire).to have_exactly(1).item + + expect(parent_relationship.source).to eq root_mount + expect(parent_relationship.target).to eq var_mount + end + + it "adds both parent autorequires for a mount with two parents" do + grandparent_relationship = log_mount.autorequire[0] + parent_relationship = log_mount.autorequire[1] + + expect(log_mount.autorequire).to have_exactly(2).items + + expect(grandparent_relationship.source).to eq root_mount + expect(grandparent_relationship.target).to eq log_mount + + expect(parent_relationship.source).to eq var_mount + expect(parent_relationship.target).to eq log_mount + end + end end diff --git a/spec/unit/type/nagios_spec.rb b/spec/unit/type/nagios_spec.rb index 8c86aae7f..84d4338de 100755 --- a/spec/unit/type/nagios_spec.rb +++ b/spec/unit/type/nagios_spec.rb @@ -3,6 +3,222 @@ require 'spec_helper' require 'puppet/external/nagios' +describe "Nagios parser" do + + NONESCAPED_SEMICOLON_COMMENT = <<-'EOL' +define host{ + use linux-server ; Name of host template to use + host_name localhost + alias localhost + address 127.0.0.1 + } + +define command{ + command_name notify-host-by-email + command_line /usr/bin/printf "%b" "***** Nagios *****\n\nNotification Type: $NOTIFICATIONTYPE$\nHost: $HOSTNAME$\nState: $HOSTSTATE$\nAddress: $HOSTADDRESS$\nInfo: $HOSTOUTPUT$\n\nDate/Time: $LONGDATETIME$\n" | /usr/bin/mail -s "** $NOTIFICATIONTYPE$ Host Alert: $HOSTNAME$ is $HOSTSTATE$ **" $CONTACTEMAIL$ + } +EOL + + LINE_COMMENT_SNIPPET = <<-'EOL' + +# This is a comment starting at the beginning of a line + +define command{ + +# This is a comment starting at the beginning of a line + + command_name command_name + +# This is a comment starting at the beginning of a line + ## --PUPPET_NAME-- (called '_naginator_name' in the manifest) command_name + + command_line command_line + +# This is a comment starting at the beginning of a line + + } + +# This is a comment starting at the beginning of a line + +EOL + + LINE_COMMENT_SNIPPET2 = <<-'EOL' + define host{ + use linux-server ; Name of host template to use + host_name localhost + alias localhost + address 127.0.0.1 + } +define command{ + command_name command_name2 + command_line command_line2 + } +EOL + + UNKNOWN_NAGIOS_OBJECT_DEFINITION = <<-'EOL' + define command2{ + command_name notify-host-by-email + command_line /usr/bin/printf "%b" "***** Nagios *****\n\nNotification Type: $NOTIFICATIONTYPE$\nHost: $HOSTNAME$\nState: $HOSTSTATE$\nAddress: $HOSTADDRESS$\nInfo: $HOSTOUTPUT$\n\nDate/Time: $LONGDATETIME$\n" | /usr/bin/mail -s "** $NOTIFICATIONTYPE$ Host Alert: $HOSTNAME$ is $HOSTSTATE$ **" $CONTACTEMAIL$ + } + EOL + + MISSING_CLOSING_CURLY_BRACKET = <<-'EOL' + define command{ + command_name notify-host-by-email + command_line /usr/bin/printf "%b" "***** Nagios *****\n\nNotification Type: $NOTIFICATIONTYPE$\nHost: $HOSTNAME$\nState: $HOSTSTATE$\nAddress: $HOSTADDRESS$\nInfo: $HOSTOUTPUT$\n\nDate/Time: $LONGDATETIME$\n" | /usr/bin/mail -s "** $NOTIFICATIONTYPE$ Host Alert: $HOSTNAME$ is $HOSTSTATE$ **" $CONTACTEMAIL$ + EOL + + ESCAPED_SEMICOLON = <<-'EOL' + define command { + command_name nagios_table_size + command_line $USER3$/check_mysql_health --hostname localhost --username nagioschecks --password nagiosCheckPWD --mode sql --name "SELECT ROUND(Data_length/1024) as Data_kBytes from INFORMATION_SCHEMA.TABLES where TABLE_NAME=\"$ARG1$\"\;" --name2 "table size" --units kBytes -w $ARG2$ -c $ARG3$ + } + EOL + + POUND_SIGN_HASH_SYMBOL_NOT_IN_FIRST_COLUMN = <<-'EOL' + define command { + command_name notify-by-irc + command_line /usr/local/bin/riseup-nagios-client.pl "$HOSTNAME$ ($SERVICEDESC$) $NOTIFICATIONTYPE$ #$SERVICEATTEMPT$ $SERVICESTATETYPE$ $SERVICEEXECUTIONTIME$s $SERVICELATENCY$s $SERVICEOUTPUT$ $SERVICEPERFDATA$" + } + EOL + + ANOTHER_ESCAPED_SEMICOLON = <<-EOL +define command { +\tcommand_line LC_ALL=en_US.UTF-8 /usr/lib/nagios/plugins/check_haproxy -u 'http://blah:blah@$HOSTADDRESS$:8080/haproxy?stats\\;csv' +\tcommand_name check_haproxy +} +EOL + + it "should parse without error" do + parser = Nagios::Parser.new + expect { + results = parser.parse(NONESCAPED_SEMICOLON_COMMENT) + }.to_not raise_error + end + + describe "when parsing a statement" do + parser = Nagios::Parser.new + results = parser.parse(NONESCAPED_SEMICOLON_COMMENT) + results.each do |obj| + it "should have the proper base type" do + obj.should be_a_kind_of(Nagios::Base) + end + end + end + + it "should raise an error when an incorrect object definition is present" do + parser = Nagios::Parser.new + expect { + results = parser.parse(UNKNOWN_NAGIOS_OBJECT_DEFINITION) + }.to raise_error Nagios::Base::UnknownNagiosType + end + + it "should raise an error when syntax is not correct" do + parser = Nagios::Parser.new + expect { + results = parser.parse(MISSING_CLOSING_CURLY_BRACKET) + }.to raise_error Nagios::Parser::SyntaxError + end + + describe "when encoutering ';'" do + it "should not throw an exception" do + parser = Nagios::Parser.new + expect { + results = parser.parse(ESCAPED_SEMICOLON) + }.to_not raise_error Nagios::Parser::SyntaxError + end + + it "should ignore it if it is a comment" do + parser = Nagios::Parser.new + results = parser.parse(NONESCAPED_SEMICOLON_COMMENT) + results[0].use.should eql("linux-server") + end + + it "should parse correctly if it is escaped" do + parser = Nagios::Parser.new + results = parser.parse(ESCAPED_SEMICOLON) + results[0].command_line.should eql("$USER3$/check_mysql_health --hostname localhost --username nagioschecks --password nagiosCheckPWD --mode sql --name \"SELECT ROUND(Data_length/1024) as Data_kBytes from INFORMATION_SCHEMA.TABLES where TABLE_NAME=\\\"$ARG1$\\\";\" --name2 \"table size\" --units kBytes -w $ARG2$ -c $ARG3$") + end + end + + describe "when encountering '#'" do + + it "should not throw an exception" do + parser = Nagios::Parser.new + expect { + results = parser.parse(POUND_SIGN_HASH_SYMBOL_NOT_IN_FIRST_COLUMN) + }.to_not raise_error Nagios::Parser::SyntaxError + end + + + it "should ignore it at the beginning of a line" do + parser = Nagios::Parser.new + results = parser.parse(LINE_COMMENT_SNIPPET) + results[0].command_line.should eql("command_line") + end + + it "should let it go anywhere else" do + parser = Nagios::Parser.new + results = parser.parse(POUND_SIGN_HASH_SYMBOL_NOT_IN_FIRST_COLUMN) + results[0].command_line.should eql("/usr/local/bin/riseup-nagios-client.pl \"$HOSTNAME$ ($SERVICEDESC$) $NOTIFICATIONTYPE$ \#$SERVICEATTEMPT$ $SERVICESTATETYPE$ $SERVICEEXECUTIONTIME$s $SERVICELATENCY$s $SERVICEOUTPUT$ $SERVICEPERFDATA$\"") + end + + end + + describe "when encountering ';' again" do + it "should not throw an exception" do + parser = Nagios::Parser.new + expect { + results = parser.parse(ANOTHER_ESCAPED_SEMICOLON) + }.to_not raise_error Nagios::Parser::SyntaxError + end + + it "should parse correctly" do + parser = Nagios::Parser.new + results = parser.parse(ANOTHER_ESCAPED_SEMICOLON) + results[0].command_line.should eql("LC_ALL=en_US.UTF-8 /usr/lib/nagios/plugins/check_haproxy -u 'http://blah:blah@$HOSTADDRESS$:8080/haproxy?stats;csv'") + end + end + + + it "should be idempotent" do + parser = Nagios::Parser.new + src = ANOTHER_ESCAPED_SEMICOLON.dup + results = parser.parse(src) + nagios_type = Nagios::Base.create(:command) + nagios_type.command_name = results[0].command_name + nagios_type.command_line = results[0].command_line + nagios_type.to_s.should eql(ANOTHER_ESCAPED_SEMICOLON) + end + +end + +describe "Nagios generator" do + + it "should escape ';'" do + param = '$USER3$/check_mysql_health --hostname localhost --username nagioschecks --password nagiosCheckPWD --mode sql --name "SELECT ROUND(Data_length/1024) as Data_kBytes from INFORMATION_SCHEMA.TABLES where TABLE_NAME=\"$ARG1$\";" --name2 "table size" --units kBytes -w $ARG2$ -c $ARG3$' + nagios_type = Nagios::Base.create(:command) + nagios_type.command_line = param + nagios_type.to_s.should eql("define command {\n\tcommand_line $USER3$/check_mysql_health --hostname localhost --username nagioschecks --password nagiosCheckPWD --mode sql --name \"SELECT ROUND(Data_length/1024) as Data_kBytes from INFORMATION_SCHEMA.TABLES where TABLE_NAME=\\\"$ARG1$\\\"\\;\" --name2 \"table size\" --units kBytes -w $ARG2$ -c $ARG3$\n}\n") + end + + it "should escape ';' if it is not already the case" do + param = "LC_ALL=en_US.UTF-8 /usr/lib/nagios/plugins/check_haproxy -u 'http://blah:blah@$HOSTADDRESS$:8080/haproxy?stats;csv'" + nagios_type = Nagios::Base.create(:command) + nagios_type.command_line = param + nagios_type.to_s.should eql("define command {\n\tcommand_line LC_ALL=en_US.UTF-8 /usr/lib/nagios/plugins/check_haproxy -u 'http://blah:blah@$HOSTADDRESS$:8080/haproxy?stats\\;csv'\n}\n") + end + + it "should be idempotent" do + param = '$USER3$/check_mysql_health --hostname localhost --username nagioschecks --password nagiosCheckPWD --mode sql --name "SELECT ROUND(Data_length/1024) as Data_kBytes from INFORMATION_SCHEMA.TABLES where TABLE_NAME=\"$ARG1$\";" --name2 "table size" --units kBytes -w $ARG2$ -c $ARG3$' + nagios_type = Nagios::Base.create(:command) + nagios_type.command_line = param + parser = Nagios::Parser.new + results = parser.parse(nagios_type.to_s) + results[0].command_line.should eql(param) + end +end + describe "Nagios resource types" do Nagios::Base.eachtype do |name, nagios_type| puppet_type = Puppet::Type.type("nagios_#{name}") diff --git a/spec/unit/type/package_spec.rb b/spec/unit/type/package_spec.rb index 5312b3f38..fefd15805 100755 --- a/spec/unit/type/package_spec.rb +++ b/spec/unit/type/package_spec.rb @@ -51,7 +51,7 @@ describe Puppet::Type.type(:package) do :clear => nil, :validate_source => nil ) - Puppet::Type.type(:package).defaultprovider.expects(:new).returns(@provider) + Puppet::Type.type(:package).defaultprovider.stubs(:new).returns(@provider) end it "should support :present as a value to :ensure" do @@ -100,6 +100,12 @@ describe Puppet::Type.type(:package) do 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 diff --git a/spec/unit/type/schedule_spec.rb b/spec/unit/type/schedule_spec.rb index 020f7d841..0610bd175 100755 --- a/spec/unit/type/schedule_spec.rb +++ b/spec/unit/type/schedule_spec.rb @@ -62,6 +62,12 @@ describe Puppet::Type.type(:schedule) do 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| diff --git a/spec/unit/type/service_spec.rb b/spec/unit/type/service_spec.rb index 630b44b75..e36d11a56 100755 --- a/spec/unit/type/service_spec.rb +++ b/spec/unit/type/service_spec.rb @@ -120,14 +120,14 @@ describe Puppet::Type.type(:service), "when validating attribute values" do end it "should split paths on '#{File::PATH_SEPARATOR}'" do - FileTest.stubs(:exist?).returns(true) + Puppet::FileSystem::File.stubs(:exist?).returns(true) FileTest.stubs(:directory?).returns(true) svc = Puppet::Type.type(:service).new(:name => "yay", :path => "/one/two#{File::PATH_SEPARATOR}/three/four") svc[:path].should == %w{/one/two /three/four} end it "should accept arrays of paths joined by '#{File::PATH_SEPARATOR}'" do - FileTest.stubs(:exist?).returns(true) + Puppet::FileSystem::File.stubs(:exist?).returns(true) FileTest.stubs(:directory?).returns(true) svc = Puppet::Type.type(:service).new(:name => "yay", :path => ["/one#{File::PATH_SEPARATOR}/two", "/three#{File::PATH_SEPARATOR}/four"]) svc[:path].should == %w{/one /two /three /four} @@ -137,7 +137,7 @@ end describe Puppet::Type.type(:service), "when setting default attribute values" do it "should default to the provider's default path if one is available" do FileTest.stubs(:directory?).returns(true) - FileTest.stubs(:exist?).returns(true) + Puppet::FileSystem::File.stubs(:exist?).returns(true) Puppet::Type.type(:service).defaultprovider.stubs(:respond_to?).returns(true) Puppet::Type.type(:service).defaultprovider.stubs(:defpath).returns("testing") diff --git a/spec/unit/type/tidy_spec.rb b/spec/unit/type/tidy_spec.rb index cf3fc828b..abe5d26ff 100755 --- a/spec/unit/type/tidy_spec.rb +++ b/spec/unit/type/tidy_spec.rb @@ -10,17 +10,15 @@ describe tidy do before do @basepath = make_absolute("/what/ever") Puppet.settings.stubs(:use) - - # for an unknown reason some of these specs fails when run individually - # with a failed expectation on File.lstat in the autoloader. - File.stubs(:lstat) end it "should use :lstat when stating a file" do - resource = tidy.new :path => "/foo/bar", :age => "1d" + path = '/foo/bar' + resource = tidy.new :path => path, :age => "1d" stat = mock 'stat' - File.expects(:lstat).with("/foo/bar").returns stat - resource.stat("/foo/bar").should == stat + stub_file = stub(path, :lstat => stat) + Puppet::FileSystem::File.expects(:new).with(path).returns stub_file + resource.stat(path).should == stat end [:age, :size, :path, :matches, :type, :recurse, :rmdirs].each do |param| @@ -130,7 +128,8 @@ describe tidy do before do @tidy = Puppet::Type.type(:tidy).new :path => @basepath @stat = stub 'stat', :ftype => "directory" - File.stubs(:lstat).with(@basepath).returns @stat + @stub_file = stub(@basepath, :lstat => @stat) + Puppet::FileSystem::File.stubs(:new).with(@basepath).returns @stub_file end describe "and generating files" do @@ -160,7 +159,7 @@ describe tidy do end it "should do nothing if the targeted file does not exist" do - File.expects(:lstat).with(@basepath).raises Errno::ENOENT + @stub_file.expects(:lstat).raises Errno::ENOENT @tidy.generate.should == [] end @@ -311,32 +310,33 @@ describe tidy do before do @tidy = Puppet::Type.type(:tidy).new :path => @basepath @stat = stub 'stat', :ftype => "file" - File.stubs(:lstat).with(@basepath).returns @stat + @stub_file = stub(@basepath, :lstat => @stat) + Puppet::FileSystem::File.expects(:new).with(@basepath).returns @stub_file end it "should not try to recurse if the file does not exist" do @tidy[:recurse] = true - File.stubs(:lstat).with(@basepath).returns nil + @stub_file.stubs(:lstat).returns nil @tidy.generate.should == [] end it "should not be tidied if the file does not exist" do - File.expects(:lstat).with(@basepath).raises Errno::ENOENT + @stub_file.expects(:lstat).raises Errno::ENOENT @tidy.should_not be_tidy(@basepath) end it "should not be tidied if the user has no access to the file" do - File.expects(:lstat).with(@basepath).raises Errno::EACCES + @stub_file.expects(:lstat).raises Errno::EACCES @tidy.should_not be_tidy(@basepath) end it "should not be tidied if it is a directory and rmdirs is set to false" do stat = mock 'stat', :ftype => "directory" - File.expects(:lstat).with(@basepath).returns stat + @stub_file.expects(:lstat).returns stat @tidy.should_not be_tidy(@basepath) end diff --git a/spec/unit/type/user_spec.rb b/spec/unit/type/user_spec.rb index dd3dca6d9..2877d25c5 100755 --- a/spec/unit/type/user_spec.rb +++ b/spec/unit/type/user_spec.rb @@ -339,6 +339,15 @@ describe Puppet::Type.type(:user) do end end + describe "when managing comment on Ruby 1.9", :if => String.respond_to?(:encode) do + it "should force value encoding to ASCII-8BIT" do + value = 'abcd'.encode(Encoding::UTF_8) + comment = described_class.new(:name => 'foo', :comment => value) + comment[:comment].should == 'abcd' + comment[:comment].encoding.should == 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 diff --git a/spec/unit/type_spec.rb b/spec/unit/type_spec.rb index b160c743f..ca29d680e 100755 --- a/spec/unit/type_spec.rb +++ b/spec/unit/type_spec.rb @@ -1,9 +1,10 @@ #! /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") @@ -63,6 +64,28 @@ describe Puppet::Type, :unless => Puppet.features.microsoft_windows? do 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 @@ -109,6 +132,36 @@ describe Puppet::Type, :unless => Puppet.features.microsoft_windows? 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") @@ -123,7 +176,8 @@ describe Puppet::Type, :unless => Puppet.features.microsoft_windows? do end it "should have tags" do - resource.tags.should == ["mount", "foo"] + expect(resource).to be_tagged("mount") + expect(resource).to be_tagged("foo") end it "should have a path" do @@ -165,13 +219,19 @@ describe Puppet::Type, :unless => Puppet.features.microsoft_windows? do @resource.event.default_log_level.should == :warning end - {:file => "/my/file", :line => 50, :tags => %{foo bar}}.each do |attr, value| + {: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 @@ -470,6 +530,28 @@ describe Puppet::Type, :unless => Puppet.features.microsoft_windows? do 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 @@ -588,7 +670,7 @@ describe Puppet::Type, :unless => Puppet.features.microsoft_windows? do resource.should be_a Puppet::Resource resource[:fstype].should == 15 resource[:remounts].should == :true - resource.tags.should =~ %w{foo bar baz mount} + resource.tags.should == Puppet::Util::TagSet.new(%w{foo bar baz mount}) end end diff --git a/spec/unit/util/adsi_spec.rb b/spec/unit/util/adsi_spec.rb index 3b851f5f1..974aba79c 100755 --- a/spec/unit/util/adsi_spec.rb +++ b/spec/unit/util/adsi_spec.rb @@ -52,13 +52,48 @@ describe Puppet::Util::ADSI do end end + describe ".sid_uri", :if => Puppet.features.microsoft_windows? 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::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::ADSI.sid_uri(sid).should == 'WinNT://S-1-5-18' + end + end + describe Puppet::Util::ADSI::User do let(:username) { 'testuser' } + let(:domain) { 'DOMAIN' } + let(:domain_username) { "#{domain}\\#{username}"} it "should generate the correct URI" do Puppet::Util::ADSI::User.uri(username).should == "WinNT://./#{username},user" end + it "should generate the correct URI for a user with a domain" do + Puppet::Util::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::ADSI::User.parse_name(username).should == [username, '.'] + end + + it "should be able to parse a username with a domain" do + Puppet::Util::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::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') @@ -76,6 +111,11 @@ describe Puppet::Util::ADSI do Puppet::Util::ADSI::User.exists?(username).should be_true end + it "should be able to check the existence of a domain user" do + Puppet::Util::ADSI.expects(:connect).with("WinNT://#{domain}/#{username},user").returns connection + Puppet::Util::ADSI::User.exists?(domain_username).should be_true + end + it "should be able to delete a user" do connection.expects(:Delete).with('user', username) @@ -85,7 +125,7 @@ describe Puppet::Util::ADSI do it "should return an enumeration of IADsUser wrapped objects" do name = 'Administrator' wmi_users = [stub('WMI', :name => name)] - Puppet::Util::ADSI.expects(:execquery).with("select name from win32_useraccount").returns(wmi_users) + Puppet::Util::ADSI.expects(:execquery).with('select name from win32_useraccount where localaccount = "TRUE"').returns(wmi_users) native_user = stub('IADsUser') homedir = "C:\\Users\\#{name}" @@ -99,7 +139,8 @@ describe Puppet::Util::ADSI do end describe "an instance" do - let(:adsi_user) { stub 'user' } + let(:adsi_user) { stub('user', :objectSID => []) } + let(:sid) { stub(:account => username, :domain => 'testcomputername') } let(:user) { Puppet::Util::ADSI::User.new(username, adsi_user) } it "should provide its groups as a list of names" do @@ -133,14 +174,16 @@ describe Puppet::Util::ADSI do user.password = 'pwd' end - it "should generate the correct URI" do - user.uri.should == "WinNT://./#{username},user" + it "should generate the correct URI",:if => Puppet.features.microsoft_windows? do + Puppet::Util::Windows::Security.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 + describe "when given a set of groups to which to add the user", :if => Puppet.features.microsoft_windows? do let(:groups_to_set) { 'group1,group2' } before(:each) do + Puppet::Util::Windows::Security.stubs(:octet_string_to_sid_object).returns(sid) user.expects(:groups).returns ['group2', 'group3'] end @@ -152,6 +195,7 @@ describe Puppet::Util::ADSI do group3 = stub 'group1' group3.expects(:Remove).with("WinNT://testcomputername/#{username},user") + Puppet::Util::ADSI.expects(:sid_uri).with(sid).returns("WinNT://testcomputername/#{username},user").twice Puppet::Util::ADSI.expects(:connect).with('WinNT://./group1,group').returns group1 Puppet::Util::ADSI.expects(:connect).with('WinNT://./group3,group').returns group3 @@ -164,6 +208,7 @@ describe Puppet::Util::ADSI do group1 = stub 'group1' group1.expects(:Add).with("WinNT://testcomputername/#{username},user") + Puppet::Util::ADSI.expects(:sid_uri).with(sid).returns("WinNT://testcomputername/#{username},user") Puppet::Util::ADSI.expects(:connect).with('WinNT://./group1,group').returns group1 user.set_groups(groups_to_set, true) @@ -179,19 +224,58 @@ describe Puppet::Util::ADSI do describe "an instance" do let(:adsi_group) { stub 'group' } let(:group) { Puppet::Util::ADSI::Group.new(groupname, adsi_group) } + let(:someone_sid){ stub(:account => 'someone', :domain => 'testcomputername')} + + it "should be able to add a member (deprecated)", :if => Puppet.features.microsoft_windows? do + Puppet.expects(:deprecation_warning).with('Puppet::Util::ADSI::Group#add_members is deprecated; please use Puppet::Util::ADSI::Group#add_member_sids') + + Puppet::Util::Windows::Security.expects(:name_to_sid_object).with('someone').returns(someone_sid) + Puppet::Util::ADSI.expects(:sid_uri).with(someone_sid).returns("WinNT://testcomputername/someone,user") - it "should be able to add a member" do adsi_group.expects(:Add).with("WinNT://testcomputername/someone,user") group.add_member('someone') end - it "should be able to remove a member" do + it "should raise when adding a member that can't resolve to a SID (deprecated)", :if => Puppet.features.microsoft_windows? do + expect { + group.add_member('foobar') + }.to raise_error(Puppet::Error, /Could not resolve username: foobar/) + end + + it "should be able to remove a member (deprecated)", :if => Puppet.features.microsoft_windows? do + Puppet.expects(:deprecation_warning).with('Puppet::Util::ADSI::Group#remove_members is deprecated; please use Puppet::Util::ADSI::Group#remove_member_sids') + + Puppet::Util::Windows::Security.expects(:name_to_sid_object).with('someone').returns(someone_sid) + Puppet::Util::ADSI.expects(:sid_uri).with(someone_sid).returns("WinNT://testcomputername/someone,user") + adsi_group.expects(:Remove).with("WinNT://testcomputername/someone,user") group.remove_member('someone') end + it "should raise when removing a member that can't resolve to a SID (deprecated)", :if => Puppet.features.microsoft_windows? do + expect { + group.remove_member('foobar') + }.to raise_error(Puppet::Error, /Could not resolve username: foobar/) + end + + describe "should be able to use SID objects", :if => Puppet.features.microsoft_windows? do + let(:system) { Puppet::Util::Windows::Security.name_to_sid_object('SYSTEM') } + + it "to add a member" do + adsi_group.expects(:Add).with("WinNT://S-1-5-18") + + group.add_member_sids(system) + end + + it "to remove a member" do + adsi_group.expects(:Remove).with("WinNT://S-1-5-18") + + group.remove_member_sids(system) + end + end + it "should provide its groups as a list of names" do names = ['user1', 'user2'] @@ -202,14 +286,38 @@ describe Puppet::Util::ADSI do group.members.should =~ names end - it "should be able to add a list of users to a group" do - names = ['user1', 'user2'] - adsi_group.expects(:Members).returns names.map{|n| stub(:Name => n)} + it "should be able to add a list of users to a group", :if => Puppet.features.microsoft_windows? 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::Security.expects(:octet_string_to_sid_object).with([0]).returns(sids[0]) + Puppet::Util::Windows::Security.expects(:octet_string_to_sid_object).with([1]).returns(sids[1]) - adsi_group.expects(:Remove).with('WinNT://testcomputername/user1,user') - adsi_group.expects(:Add).with('WinNT://testcomputername/user3,user') + Puppet::Util::Windows::Security.expects(:name_to_sid_object).with('user2').returns(sids[1]) + Puppet::Util::Windows::Security.expects(:name_to_sid_object).with('DOMAIN2\user3').returns(sids[2]) + + Puppet::Util::ADSI.expects(:sid_uri).with(sids[0]).returns("WinNT://DOMAIN/user1,user") + Puppet::Util::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 - group.set_members(['user2', 'user3']) + it "should raise an error when a username does not resolve to a SID", :if => Puppet.features.microsoft_windows? 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 @@ -248,7 +356,7 @@ describe Puppet::Util::ADSI do it "should return an enumeration of IADsGroup wrapped objects" do name = 'Administrators' wmi_groups = [stub('WMI', :name => name)] - Puppet::Util::ADSI.expects(:execquery).with("select name from win32_group").returns(wmi_groups) + Puppet::Util::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')]) diff --git a/spec/unit/util/autoload_spec.rb b/spec/unit/util/autoload_spec.rb index 933855914..4a0782ac7 100755 --- a/spec/unit/util/autoload_spec.rb +++ b/spec/unit/util/autoload_spec.rb @@ -71,7 +71,7 @@ describe Puppet::Util::Autoload do [RuntimeError, LoadError, SyntaxError].each do |error| it "should die with Puppet::Error if a #{error.to_s} exception is thrown" do - File.stubs(:exist?).returns true + Puppet::FileSystem::File.stubs(:exist?).returns true Kernel.expects(:load).raises error @@ -84,7 +84,7 @@ describe Puppet::Util::Autoload do end it "should register loaded files with the autoloader" do - File.stubs(:exist?).returns true + Puppet::FileSystem::File.stubs(:exist?).returns true Kernel.stubs(:load) @autoload.load("myfile") @@ -94,7 +94,7 @@ describe Puppet::Util::Autoload do end it "should be seen by loaded? on the instance using the short name" do - File.stubs(:exist?).returns true + Puppet::FileSystem::File.stubs(:exist?).returns true Kernel.stubs(:load) @autoload.load("myfile") @@ -104,7 +104,7 @@ describe Puppet::Util::Autoload do end it "should register loaded files with the main loaded file list so they are not reloaded by ruby" do - File.stubs(:exist?).returns true + Puppet::FileSystem::File.stubs(:exist?).returns true Kernel.stubs(:load) @autoload.load("myfile") @@ -117,7 +117,7 @@ describe Puppet::Util::Autoload do it "should load the first file in the searchpath" do @autoload.stubs(:search_directories).returns [make_absolute("/a"), make_absolute("/b")] FileTest.stubs(:directory?).returns true - File.stubs(:exist?).returns true + Puppet::FileSystem::File.stubs(:exist?).returns true Kernel.expects(:load).with(make_absolute("/a/tmp/myfile.rb"), optionally(anything)) @autoload.load("myfile") @@ -126,7 +126,7 @@ describe Puppet::Util::Autoload do end it "should treat equivalent paths to a loaded file as loaded" do - File.stubs(:exist?).returns true + Puppet::FileSystem::File.stubs(:exist?).returns true Kernel.stubs(:load) @autoload.load("myfile") @@ -144,7 +144,7 @@ describe Puppet::Util::Autoload do @autoload.class.stubs(:search_directories).returns [make_absolute("/a")] FileTest.stubs(:directory?).returns true Dir.stubs(:glob).returns [make_absolute("/a/foo/file.rb")] - File.stubs(:exist?).returns true + Puppet::FileSystem::File.stubs(:exist?).returns true @time_a = Time.utc(2010, 'jan', 1, 6, 30) File.stubs(:mtime).returns @time_a @@ -185,7 +185,7 @@ describe Puppet::Util::Autoload do it "changes should be seen by changed? on the instance using the short name" do File.stubs(:mtime).returns(@first_time) - File.stubs(:exist?).returns true + Puppet::FileSystem::File.stubs(:exist?).returns true Kernel.stubs(:load) @autoload.load("myfile") @autoload.loaded?("myfile").should be @@ -206,14 +206,14 @@ describe Puppet::Util::Autoload do it "should reload if mtime changes" do File.stubs(:mtime).with(@file_a).returns(@first_time + 60) - File.stubs(:exist?).with(@file_a).returns true + Puppet::FileSystem::File.stubs(:exist?).with(@file_a).returns true Kernel.expects(:load).with(@file_a, optionally(anything)) @autoload.class.reload_changed end it "should do nothing if the file is deleted" do File.stubs(:mtime).with(@file_a).raises(Errno::ENOENT) - File.stubs(:exist?).with(@file_a).returns false + Puppet::FileSystem::File.stubs(:exist?).with(@file_a).returns false Kernel.expects(:load).never @autoload.class.reload_changed end @@ -228,8 +228,8 @@ describe Puppet::Util::Autoload do File.expects(:mtime).with(@file_a).returns(@first_time) @autoload.class.mark_loaded("file", @file_a) File.stubs(:mtime).with(@file_a).raises(Errno::ENOENT) - File.stubs(:exist?).with(@file_a).returns false - File.stubs(:exist?).with(@file_b).returns true + Puppet::FileSystem::File.stubs(:exist?).with(@file_a).returns false + Puppet::FileSystem::File.stubs(:exist?).with(@file_b).returns true File.stubs(:mtime).with(@file_b).returns @first_time Kernel.expects(:load).with(@file_b, optionally(anything)) @autoload.class.reload_changed @@ -238,11 +238,11 @@ describe Puppet::Util::Autoload do it "should load a/file when b/file is loaded and a/file is created" do File.stubs(:mtime).with(@file_b).returns @first_time - File.stubs(:exist?).with(@file_b).returns true + Puppet::FileSystem::File.stubs(:exist?).with(@file_b).returns true @autoload.class.mark_loaded("file", @file_b) File.stubs(:mtime).with(@file_a).returns @first_time - File.stubs(:exist?).with(@file_a).returns true + Puppet::FileSystem::File.stubs(:exist?).with(@file_a).returns true Kernel.expects(:load).with(@file_a, optionally(anything)) @autoload.class.reload_changed @autoload.class.send(:loaded)["file"].should == [@file_a, @first_time] diff --git a/spec/unit/util/backups_spec.rb b/spec/unit/util/backups_spec.rb index 8d3ea1956..654ddb788 100755 --- a/spec/unit/util/backups_spec.rb +++ b/spec/unit/util/backups_spec.rb @@ -20,7 +20,7 @@ describe Puppet::Util::Backups do file = Puppet::Type.type(:file).new(:name => path) file.expects(:bucket).never - FileTest.expects(:exists?).with(path).returns false + Puppet::FileSystem::File.expects(:exist?).with(path).returns false file.perform_backup end @@ -29,23 +29,25 @@ describe Puppet::Util::Backups do file = Puppet::Type.type(:file).new(:name => path, :backup => false) file.expects(:bucket).never - FileTest.expects(:exists?).never + Puppet::FileSystem::File.expects(:exist?).never file.perform_backup end it "a bucket should be used when provided" do - File.stubs(:lstat).with(path).returns(mock('lstat', :ftype => 'file')) + stub_file = stub(path, :lstat => mock('lstat', :ftype => 'file')) + Puppet::FileSystem::File.expects(:new).with(path).returns stub_file bucket.expects(:backup).with(path).returns("mysum") - FileTest.expects(:exists?).with(path).returns(true) + Puppet::FileSystem::File.expects(:exist?).with(path).returns(true) file.perform_backup end it "should propagate any exceptions encountered when backing up to a filebucket" do - File.stubs(:lstat).with(path).returns(mock('lstat', :ftype => 'file')) + stub_file = stub(path, :lstat => mock('lstat', :ftype => 'file')) + Puppet::FileSystem::File.expects(:new).with(path).returns stub_file bucket.expects(:backup).raises ArgumentError - FileTest.expects(:exists?).with(path).returns(true) + Puppet::FileSystem::File.expects(:exist?).with(path).returns(true) lambda { file.perform_backup }.should raise_error(ArgumentError) end @@ -56,35 +58,39 @@ describe Puppet::Util::Backups do let(:file) { Puppet::Type.type(:file).new(:name => path, :backup => '.'+ext) } it "should remove any local backup if one exists" do - File.expects(:lstat).with(backup).returns stub("stat", :ftype => "file") - File.expects(:unlink).with(backup) + stub_file = stub(backup, :lstat => stub('stat', :ftype => 'file')) + Puppet::FileSystem::File.expects(:new).with(backup).returns stub_file + Puppet::FileSystem::File.expects(:unlink).with(backup) FileUtils.stubs(:cp_r) - FileTest.expects(:exists?).with(path).returns(true) + Puppet::FileSystem::File.expects(:exist?).with(path).returns(true) file.perform_backup end it "should fail when the old backup can't be removed" do - File.expects(:lstat).with(backup).returns stub("stat", :ftype => "file") - File.expects(:unlink).with(backup).raises ArgumentError + stub_file = stub(backup, :lstat => stub('stat', :ftype => 'file')) + Puppet::FileSystem::File.expects(:new).with(backup).returns stub_file + Puppet::FileSystem::File.expects(:unlink).with(backup).raises ArgumentError FileUtils.expects(:cp_r).never - FileTest.expects(:exists?).with(path).returns(true) + Puppet::FileSystem::File.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 - File.expects(:lstat).with(backup).raises(Errno::ENOENT) - File.expects(:unlink).with(backup).never + stub_file = stub(backup) + Puppet::FileSystem::File.expects(:new).with(backup).returns stub_file + stub_file.expects(:lstat).raises(Errno::ENOENT) + Puppet::FileSystem::File.expects(:unlink).with(backup).never FileUtils.stubs(:cp_r) - FileTest.expects(:exists?).with(path).returns(true) + Puppet::FileSystem::File.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) - FileTest.stubs(:exists?).with(path).returns(true) + Puppet::FileSystem::File.stubs(:exist?).with(path).returns(true) file.perform_backup.should be_true end @@ -92,7 +98,7 @@ describe Puppet::Util::Backups do it "should propagate exceptions if no backup can be created" do FileUtils.expects(:cp_r).raises ArgumentError - FileTest.stubs(:exists?).with(path).returns(true) + Puppet::FileSystem::File.stubs(:exist?).with(path).returns(true) lambda { file.perform_backup }.should raise_error(Puppet::Error) end end @@ -108,10 +114,11 @@ describe Puppet::Util::Backups do bucket.expects(:backup).with(filename).returns true - File.stubs(:lstat).with(path).returns(stub('lstat', :ftype => 'directory')) + stub_file = stub(path, :lstat => stub('stat', :ftype => 'directory')) + Puppet::FileSystem::File.expects(:new).with(path).returns stub_file - FileTest.stubs(:exists?).with(path).returns(true) - FileTest.stubs(:exists?).with(filename).returns(true) + Puppet::FileSystem::File.stubs(:exist?).with(path).returns(true) + Puppet::FileSystem::File.stubs(:exist?).with(filename).returns(true) file.perform_backup end @@ -120,7 +127,8 @@ describe Puppet::Util::Backups do file = Puppet::Type.type(:file).new(:name => path, :backup => 'foo', :recurse => true) bucket.expects(:backup).never - File.stubs(:stat).with(path).returns(stub('stat', :ftype => 'directory')) + stub_file = stub('file', :stat => stub('stat', :ftype => 'directory')) + Puppet::FileSystem::File.stubs(:new).with(path).returns stub_file Find.expects(:find).never file.perform_backup diff --git a/spec/unit/util/checksums_spec.rb b/spec/unit/util/checksums_spec.rb index e928ca8ae..0adff7a9a 100755 --- a/spec/unit/util/checksums_spec.rb +++ b/spec/unit/util/checksums_spec.rb @@ -132,7 +132,8 @@ describe Puppet::Util::Checksums do file = "/my/file" stat = mock 'stat', sum => "mysum" - File.expects(:stat).with(file).returns(stat) + stub_file = stub(file, :stat => stat) + Puppet::FileSystem::File.expects(:new).with(file).returns stub_file @summer.send(sum.to_s + "_file", file).should == "mysum" end diff --git a/spec/unit/util/command_line_spec.rb b/spec/unit/util/command_line_spec.rb index 3d8f896d1..6ba8077c2 100755 --- a/spec/unit/util/command_line_spec.rb +++ b/spec/unit/util/command_line_spec.rb @@ -143,5 +143,46 @@ describe Puppet::Util::CommandLine do Puppet::Util::CommandLine.available_subcommands end end + + describe 'when setting process priority' do + let(:command_line) do + Puppet::Util::CommandLine.new("puppet", %w{ agent }) + end + + before :each do + Puppet::Util::CommandLine::ApplicationSubcommand.any_instance.stubs(:run) + end + + it 'should never set priority by default' do + Process.expects(:setpriority).never + + command_line.execute + end + + it 'should lower the process priority if one has been specified' do + Puppet[:priority] = 10 + + Process.expects(:setpriority).with(0, Process.pid, 10) + command_line.execute + end + + it 'should warn if trying to raise priority, but not privileged user' do + Puppet[:priority] = -10 + + Process.expects(:setpriority).raises(Errno::EACCES, 'Permission denied') + Puppet.expects(:warning).with("Failed to set process priority to '-10'") + + command_line.execute + end + + it "should warn if the platform doesn't support `Process.setpriority`" do + Puppet[:priority] = 15 + + Process.expects(:setpriority).raises(NotImplementedError, 'NotImplementedError: setpriority() function is unimplemented on this machine') + Puppet.expects(:warning).with("Failed to set process priority to '15'") + + command_line.execute + end + end end end diff --git a/spec/unit/util/docs_spec.rb b/spec/unit/util/docs_spec.rb new file mode 100644 index 000000000..eb736ff72 --- /dev/null +++ b/spec/unit/util/docs_spec.rb @@ -0,0 +1,91 @@ +require 'spec_helper' + +describe Puppet::Util::Docs do + + describe '.scrub' do + let(:my_cleaned_output) do + %q{This resource type uses the prescribed native tools for creating +groups and generally uses POSIX APIs for retrieving information +about them. It does not directly modify `/etc/passwd` or anything. + +* Just for fun, we'll add a list. +* list item two, + which has some add'l lines included in it. + +And here's a code block: + + this is the piece of code + it does something cool + +**Autorequires:** I would be listing autorequired resources here.} + end + + it "strips the least common indent from multi-line strings, without mangling indentation beyond the least common indent" do + input = <<EOT + This resource type uses the prescribed native tools for creating + groups and generally uses POSIX APIs for retrieving information + about them. It does not directly modify `/etc/passwd` or anything. + + * Just for fun, we'll add a list. + * list item two, + which has some add'l lines included in it. + + And here's a code block: + + this is the piece of code + it does something cool + + **Autorequires:** I would be listing autorequired resources here. +EOT + output = Puppet::Util::Docs.scrub(input) + expect(output).to eq my_cleaned_output + end + + it "ignores the first line when calculating least common indent" do + input = "This resource type uses the prescribed native tools for creating + groups and generally uses POSIX APIs for retrieving information + about them. It does not directly modify `/etc/passwd` or anything. + + * Just for fun, we'll add a list. + * list item two, + which has some add'l lines included in it. + + And here's a code block: + + this is the piece of code + it does something cool + + **Autorequires:** I would be listing autorequired resources here." + output = Puppet::Util::Docs.scrub(input) + expect(output).to eq my_cleaned_output + end + + it "strips trailing whitespace from each line, and strips trailing newlines at end" do + input = "This resource type uses the prescribed native tools for creating \n groups and generally uses POSIX APIs for retrieving information \n about them. It does not directly modify `/etc/passwd` or anything. \n\n * Just for fun, we'll add a list. \n * list item two,\n which has some add'l lines included in it. \n\n And here's a code block:\n\n this is the piece of code \n it does something cool \n\n **Autorequires:** I would be listing autorequired resources here. \n\n" + output = Puppet::Util::Docs.scrub(input) + expect(output).to eq my_cleaned_output + end + + it "has no side effects on original input string" do + input = "First line \n second line \n \n indented line \n \n last line\n\n" + clean_input = "First line \n second line \n \n indented line \n \n last line\n\n" + not_used = Puppet::Util::Docs.scrub(input) + expect(input).to eq clean_input + end + + it "does not include whitespace-only lines when calculating least common indent" do + input = "First line\n second line\n \n indented line\n\n last line" + expected_output = "First line\nsecond line\n\n indented line\n\nlast line" + #bogus_output = "First line\nsecond line\n\n indented line\n\nlast line" + output = Puppet::Util::Docs.scrub(input) + expect(output).to eq expected_output + end + + it "accepts a least common indent of zero, thus not adding errors when input string is already scrubbed" do + expect(Puppet::Util::Docs.scrub(my_cleaned_output)).to eq my_cleaned_output + end + + + end +end + diff --git a/spec/unit/util/execution_spec.rb b/spec/unit/util/execution_spec.rb index 71af8f01f..7bb15cd75 100755 --- a/spec/unit/util/execution_spec.rb +++ b/spec/unit/util/execution_spec.rb @@ -306,11 +306,21 @@ describe Puppet::Util::Execution do Puppet::Util::Execution.stubs(:execute_windows).returns(proc_info_stub) Puppet::Util::Windows::Process.expects(:wait_process).with(process_handle).raises('whatever') - Process.expects(:CloseHandle).with(thread_handle) - Process.expects(:CloseHandle).with(process_handle) + Puppet::Util::Windows::Process.expects(:CloseHandle).with(thread_handle) + Puppet::Util::Windows::Process.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 @@ -509,7 +519,7 @@ describe Puppet::Util::Execution do Tempfile.stubs(:new).returns(stdout) stdout.write("My expected command output") - Puppet::Util::Execution.execute('test command', :squelch => true).should == nil + Puppet::Util::Execution.execute('test command', :squelch => true).should == '' end it "should delete the file used for output if squelch is false" do @@ -519,7 +529,7 @@ describe Puppet::Util::Execution do Puppet::Util::Execution.execute('test command') - File.should_not be_exist(path) + Puppet::FileSystem::File.exist?(path).should be_false end it "should not raise an error if the file is open" do @@ -591,6 +601,20 @@ describe Puppet::Util::Execution do 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') + $CHILD_STATUS.expects(:==).with(0).returns(true) + 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') + $CHILD_STATUS.expects(:==).with(0).returns(true) + 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') $CHILD_STATUS.expects(:==).with(0).returns(true) diff --git a/spec/unit/util/filetype_spec.rb b/spec/unit/util/filetype_spec.rb index ecf7c3690..5d8f0b36d 100755 --- a/spec/unit/util/filetype_spec.rb +++ b/spec/unit/util/filetype_spec.rb @@ -16,15 +16,15 @@ describe Puppet::Util::FileType do describe "when the file already exists" do it "should return the file's contents when asked to read it" do - File.expects(:exist?).with(path).returns true + Puppet::FileSystem::File.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 - File.expects(:exist?).with(path).returns true - File.expects(:unlink).with(path) + Puppet::FileSystem::File.expects(:exist?).with(path).returns true + Puppet::FileSystem::File.expects(:unlink).with(path) file.remove end @@ -32,7 +32,7 @@ describe Puppet::Util::FileType do describe "when the file does not exist" do it "should return an empty string when asked to read the file" do - File.expects(:exist?).with(path).returns false + Puppet::FileSystem::File.expects(:exist?).with(path).returns false file.read.should == "" end @@ -63,13 +63,13 @@ describe Puppet::Util::FileType do describe "when backing up a file" do it "should do nothing if the file does not exist" do - File.expects(:exists?).with(path).returns false + Puppet::FileSystem::File.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 - File.expects(:exists?).with(path).returns true + Puppet::FileSystem::File.expects(:exist?).with(path).returns true bucket = mock 'bucket' bucket.expects(:backup).with(path) @@ -155,7 +155,7 @@ describe Puppet::Util::FileType do end after :each do - File.should_not be_exist @tmp_cron_path + Puppet::FileSystem::File.exist?(@tmp_cron_path).should be_false end it "should run crontab as the target user on a temporary file" do diff --git a/spec/unit/util/lockfile_spec.rb b/spec/unit/util/lockfile_spec.rb index 4f3463429..8d10d5106 100644 --- a/spec/unit/util/lockfile_spec.rb +++ b/spec/unit/util/lockfile_spec.rb @@ -25,7 +25,7 @@ describe Puppet::Util::Lockfile do it "should create a lock file" do @lock.lock - File.should be_exists(@lockfile) + Puppet::FileSystem::File.exist?(@lockfile).should be_true end it "should create a lock file containing a string" do @@ -49,7 +49,7 @@ describe Puppet::Util::Lockfile do it "should clear the lock file" do File.open(@lockfile, 'w') { |fd| fd.print("locked") } @lock.unlock - File.should_not be_exists(@lockfile) + Puppet::FileSystem::File.exist?(@lockfile).should be_false end end diff --git a/spec/unit/util/log/destinations_spec.rb b/spec/unit/util/log/destinations_spec.rb index 332e8ad7c..b34b973cf 100755 --- a/spec/unit/util/log/destinations_spec.rb +++ b/spec/unit/util/log/destinations_spec.rb @@ -108,6 +108,38 @@ describe Puppet::Util::Log.desttypes[:syslog] do 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] } diff --git a/spec/unit/util/monkey_patches_spec.rb b/spec/unit/util/monkey_patches_spec.rb index cc487020c..13e10454c 100755 --- a/spec/unit/util/monkey_patches_spec.rb +++ b/spec/unit/util/monkey_patches_spec.rb @@ -280,6 +280,47 @@ describe OpenSSL::SSL::SSLContext do end end + +describe OpenSSL::X509::Store, :if => Puppet::Util::Platform.windows? do + let(:store) { described_class.new } + let(:cert) { OpenSSL::X509::Certificate.new(File.read(my_fixture('x509.pem'))) } + + def with_root_certs(certs) + Puppet::Util::Windows::RootCerts.expects(:instance).returns(certs) + end + + it "adds a root cert to the store" do + with_root_certs([cert]) + + store.set_default_paths + end + + it "ignores duplicate root certs" do + with_root_certs([cert, cert]) + + store.expects(:add_cert).with(cert).once + + store.set_default_paths + end + + it "warns when adding a certificate that already exists" do + with_root_certs([cert]) + store.add_cert(cert) + + store.expects(:warn).with('Failed to add /DC=com/DC=microsoft/CN=Microsoft Root Certificate Authority') + + store.set_default_paths + end + + it "raises when adding an invalid certificate" do + with_root_certs(['notacert']) + + expect { + store.set_default_paths + }.to raise_error(TypeError) + end +end + describe SecureRandom do it 'generates a properly formatted uuid' do SecureRandom.uuid.should =~ /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i diff --git a/spec/unit/util/pidlock_spec.rb b/spec/unit/util/pidlock_spec.rb index 3e70ce008..e9c28f2b0 100644 --- a/spec/unit/util/pidlock_spec.rb +++ b/spec/unit/util/pidlock_spec.rb @@ -47,7 +47,7 @@ describe Puppet::Util::Pidlock do it "should create a lock file" do @lock.lock - File.should be_exists(@lockfile) + Puppet::FileSystem::File.exist?(@lockfile).should be_true end it "should expose the lock file_path" do @@ -74,7 +74,7 @@ describe Puppet::Util::Pidlock do it "should get rid of the lock file" do @lock.lock @lock.unlock - File.should_not be_exists(@lockfile) + Puppet::FileSystem::File.exist?(@lockfile).should be_false end end @@ -106,12 +106,12 @@ describe Puppet::Util::Pidlock do describe "#lock" do it "should clear stale locks" do @lock.locked? - File.should_not be_exists(@lockfile) + Puppet::FileSystem::File.exist?(@lockfile).should be_false end it "should replace with new locks" do @lock.lock - File.should be_exists(@lockfile) + Puppet::FileSystem::File.exist?(@lockfile).should be_true @lock.lock_pid.should == 6789 @lock.should be_mine @lock.should be_locked @@ -125,7 +125,7 @@ describe Puppet::Util::Pidlock do it "should not remove the lock file" do @lock.unlock - File.should be_exists(@lockfile) + Puppet::FileSystem::File.exist?(@lockfile).should be_true end end end @@ -170,7 +170,7 @@ describe Puppet::Util::Pidlock do it "should not remove the lock file" do @lock.unlock - File.should be_exists(@lockfile) + Puppet::FileSystem::File.exist?(@lockfile).should be_true end it "should still not be our lock" do diff --git a/spec/unit/util/rdoc/parser_spec.rb b/spec/unit/util/rdoc/parser_spec.rb index 476d15214..1699020a7 100755 --- a/spec/unit/util/rdoc/parser_spec.rb +++ b/spec/unit/util/rdoc/parser_spec.rb @@ -13,7 +13,9 @@ describe "RDoc::Parser", :if => Puppet.features.rdoc1? do include PuppetSpec::Files before :each do - File.stubs(:stat).with("init.pp") + stub_file = stub('init.pp', :stat => stub()) + # Ruby 1.8.7 needs the following call to be stubs and not expects + Puppet::FileSystem::File.stubs(:new).with('init.pp').returns stub_file @top_level = stub_everything 'toplevel', :file_relative_name => "init.pp" @parser = RDoc::Parser.new(@top_level, "module/manifests/init.pp", nil, Options.instance, RDoc::Stats.new) end @@ -82,7 +84,7 @@ describe "RDoc::Parser", :if => Puppet.features.rdoc1? do File.stubs(:open).returns("readme") @parser.stubs(:parse_elements) - @module.expects(:comment=).with("readme") + @module.expects(:add_comment).with("readme", "module/manifests/init.pp") @parser.scan_top_level(@topcontainer) end @@ -93,7 +95,7 @@ describe "RDoc::Parser", :if => Puppet.features.rdoc1? do File.stubs(:open).returns("readme") @parser.stubs(:parse_elements) - @module.expects(:comment=).with("readme") + @module.expects(:add_comment).with("readme", "module/manifests/init.pp") @parser.scan_top_level(@topcontainer) end @@ -105,7 +107,7 @@ describe "RDoc::Parser", :if => Puppet.features.rdoc1? do File.stubs(:open).with("module/README.rdoc", "r").returns("readme.rdoc") @parser.stubs(:parse_elements) - @module.expects(:comment=).with("readme.rdoc") + @module.expects(:add_comment).with("readme.rdoc", "module/manifests/init.pp") @parser.scan_top_level(@topcontainer) end @@ -311,7 +313,7 @@ describe "RDoc::Parser", :if => Puppet.features.rdoc1? do end it "should associate the node documentation to the rdoc node" do - @rdoc_node.expects(:comment=).with("mydoc") + @rdoc_node.expects(:add_comment).with("mydoc", "file") @parser.document_node("mynode", @node, @class) end @@ -360,7 +362,7 @@ describe "RDoc::Parser", :if => Puppet.features.rdoc1? do end it "should associate the node documentation to the rdoc class" do - @rdoc_class.expects(:comment=).with("mydoc") + @rdoc_class.expects(:add_comment).with("mydoc", "file") @parser.document_class("mynode", @class, @module) end @@ -397,7 +399,7 @@ describe "RDoc::Parser", :if => Puppet.features.rdoc1? do before(:each) do @class = stub_everything 'class' @code = stub_everything 'code' - @code.stubs(:is_a?).with(Puppet::Parser::AST::ASTArray).returns(true) + @code.stubs(:is_a?).with(Puppet::Parser::AST::BlockExpression).returns(true) end it "should also scan mono-instruction code" do @@ -435,7 +437,7 @@ describe "RDoc::Parser", :if => Puppet.features.rdoc1? do before(:each) do @class = stub_everything 'class' @code = stub_everything 'code' - @code.stubs(:is_a?).with(Puppet::Parser::AST::ASTArray).returns(true) + @code.stubs(:is_a?).with(Puppet::Parser::AST::BlockExpression).returns(true) end it "should also scan mono-instruction code" do @@ -457,11 +459,11 @@ describe "RDoc::Parser", :if => Puppet.features.rdoc1? do @class = stub_everything 'class' @stmt = stub_everything 'stmt', :name => "myvar", :value => "myvalue", :doc => "mydoc" - @stmt.stubs(:is_a?).with(Puppet::Parser::AST::ASTArray).returns(false) + @stmt.stubs(:is_a?).with(Puppet::Parser::AST::BlockExpression).returns(false) @stmt.stubs(:is_a?).with(Puppet::Parser::AST::VarDef).returns(true) @code = stub_everything 'code' - @code.stubs(:is_a?).with(Puppet::Parser::AST::ASTArray).returns(true) + @code.stubs(:is_a?).with(Puppet::Parser::AST::BlockExpression).returns(true) end it "should recursively register variables to the current container" do @@ -483,17 +485,17 @@ describe "RDoc::Parser", :if => Puppet.features.rdoc1? do @class = stub_everything 'class' @stmt = Puppet::Parser::AST::Resource.new( :type => "File", - :instances => Puppet::Parser::AST::ASTArray.new(:children => [ + :instances => Puppet::Parser::AST::BlockExpression.new(:children => [ Puppet::Parser::AST::ResourceInstance.new( :title => Puppet::Parser::AST::Name.new(:value => "myfile"), - :parameters => Puppet::Parser::AST::ASTArray.new(:children => []) + :parameters => Puppet::Parser::AST::BlockExpression.new(:children => []) ) ]), :doc => 'mydoc' ) @code = stub_everything 'code' - @code.stubs(:is_a?).with(Puppet::Parser::AST::ASTArray).returns(true) + @code.stubs(:is_a?).with(Puppet::Parser::AST::BlockExpression).returns(true) end it "should register a PuppetResource to the current container" do diff --git a/spec/unit/util/rdoc_spec.rb b/spec/unit/util/rdoc_spec.rb index b13591374..f71bf7939 100755 --- a/spec/unit/util/rdoc_spec.rb +++ b/spec/unit/util/rdoc_spec.rb @@ -5,15 +5,7 @@ require 'puppet/util/rdoc' require 'rdoc/rdoc' describe Puppet::Util::RDoc do - it "should fail with a clear error without RDoc 1.*" do - Puppet.features.stubs(:rdoc1?).returns(false) - - expect { - Puppet::Util::RDoc.rdoc("output", []) - }.to raise_error(/the version of RDoc .* is not supported/) - end - - describe "when generating RDoc HTML documentation", :if => Puppet.features.rdoc1? do + describe "when generating RDoc HTML documentation" do before :each do @rdoc = stub_everything 'rdoc' RDoc::RDoc.stubs(:new).returns(@rdoc) @@ -24,12 +16,6 @@ describe Puppet::Util::RDoc do Puppet::Util::RDoc.rdoc("output", []) end - it "should install the Puppet HTML Generator into RDoc generators" do - Puppet::Util::RDoc.rdoc("output", []) - - RDoc::RDoc::GENERATORS["puppet"].file_name.should == "puppet/util/rdoc/generators/puppet_generator.rb" - end - it "should tell RDoc to generate documentation using the Puppet generator" do @rdoc.expects(:document).with { |args| args.include?("--fmt") and args.include?("puppet") } @@ -48,18 +34,26 @@ describe Puppet::Util::RDoc do Puppet::Util::RDoc.rdoc("output", [], "utf-8") end - it "should tell RDoc to force updates of indices when RDoc supports it" do - Options::OptionList.stubs(:options).returns([["--force-update", "-U", 0 ]]) - @rdoc.expects(:document).with { |args| args.include?("--force-update") } + describe "with rdoc1", :if => Puppet.features.rdoc1? do + it "should install the Puppet HTML Generator into RDoc generators" do + Puppet::Util::RDoc.rdoc("output", []) - Puppet::Util::RDoc.rdoc("output", []) - end + RDoc::RDoc::GENERATORS["puppet"].file_name.should == "puppet/util/rdoc/generators/puppet_generator.rb" + end - it "should not tell RDoc to force updates of indices when RDoc doesn't support it" do - Options::OptionList.stubs(:options).returns([]) - @rdoc.expects(:document).never.with { |args| args.include?("--force-update") } + it "should tell RDoc to force updates of indices when RDoc supports it" do + ::Options::OptionList.stubs(:options).returns([["--force-update", "-U", 0 ]]) + @rdoc.expects(:document).with { |args| args.include?("--force-update") } - Puppet::Util::RDoc.rdoc("output", []) + Puppet::Util::RDoc.rdoc("output", []) + end + + it "should not tell RDoc to force updates of indices when RDoc doesn't support it" do + ::Options::OptionList.stubs(:options).returns([]) + @rdoc.expects(:document).never.with { |args| args.include?("--force-update") } + + Puppet::Util::RDoc.rdoc("output", []) + end end it "should tell RDoc to use the given outputdir" do diff --git a/spec/unit/util/resource_template_spec.rb b/spec/unit/util/resource_template_spec.rb index ee4c1b866..0e6037119 100755 --- a/spec/unit/util/resource_template_spec.rb +++ b/spec/unit/util/resource_template_spec.rb @@ -6,20 +6,20 @@ require 'puppet/util/resource_template' describe Puppet::Util::ResourceTemplate do describe "when initializing" do it "should fail if the template does not exist" do - FileTest.expects(:exist?).with("/my/template").returns false + Puppet::FileSystem::File.expects(:exist?).with("/my/template").returns false lambda { Puppet::Util::ResourceTemplate.new("/my/template", mock('resource')) }.should raise_error(ArgumentError) end it "should not create the ERB template" do ERB.expects(:new).never - FileTest.expects(:exist?).with("/my/template").returns true + Puppet::FileSystem::File.expects(:exist?).with("/my/template").returns true Puppet::Util::ResourceTemplate.new("/my/template", mock('resource')) end end describe "when evaluating" do before do - FileTest.stubs(:exist?).returns true + Puppet::FileSystem::File.stubs(:exist?).returns true File.stubs(:read).returns "eh" @template = stub 'template', :result => nil diff --git a/spec/unit/util/selinux_spec.rb b/spec/unit/util/selinux_spec.rb index ac2bc2977..1357b981a 100755 --- a/spec/unit/util/selinux_spec.rb +++ b/spec/unit/util/selinux_spec.rb @@ -123,7 +123,8 @@ describe Puppet::Util::SELinux do it "should return a context if a default context exists" do self.expects(:selinux_support?).returns true fstat = stub 'File::Stat', :mode => 0 - File.expects(:lstat).with("/foo").returns fstat + stub_file = stub('/foo', :lstat => fstat) + Puppet::FileSystem::File.expects(:new).with('/foo').returns stub_file 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" @@ -150,7 +151,8 @@ describe Puppet::Util::SELinux do it "should return nil if matchpathcon returns failure" do self.expects(:selinux_support?).returns true fstat = stub 'File::Stat', :mode => 0 - File.expects(:lstat).with("/foo").returns fstat + stub_file = stub('/foo', :lstat => fstat) + Puppet::FileSystem::File.expects(:new).with('/foo').returns stub_file self.expects(:find_fs).with("/foo").returns "ext3" Selinux.expects(:matchpathcon).with("/foo", 0).returns -1 get_selinux_default_context("/foo").should be_nil diff --git a/spec/unit/util/storage_spec.rb b/spec/unit/util/storage_spec.rb index ba1675981..582bd2422 100755 --- a/spec/unit/util/storage_spec.rb +++ b/spec/unit/util/storage_spec.rb @@ -77,7 +77,7 @@ describe Puppet::Util::Storage do end it "should not fail to load" do - FileTest.exists?(@path).should be_false + Puppet::FileSystem::File.exist?(@path).should be_false Puppet[:statedir] = @path Puppet::Util::Storage.load Puppet[:statefile] = @path @@ -85,7 +85,7 @@ describe Puppet::Util::Storage do end it "should not lose its internal state when load() is called" do - FileTest.exists?(@path).should be_false + Puppet::FileSystem::File.exist?(@path).should be_false Puppet::Util::Storage.cache(:yayness) Puppet::Util::Storage.state.should == {:yayness=>{}} @@ -176,12 +176,12 @@ describe Puppet::Util::Storage do end it "should create the state file if it does not exist" do - FileTest.exists?(Puppet[:statefile]).should be_false + Puppet::FileSystem::File.exist?(Puppet[:statefile]).should be_false Puppet::Util::Storage.cache(:yayness) Puppet::Util::Storage.store - FileTest.exists?(Puppet[:statefile]).should be_true + Puppet::FileSystem::File.exist?(Puppet[:statefile]).should be_true end it "should raise an exception if the state file is not a regular file" do diff --git a/spec/unit/util/suidmanager_spec.rb b/spec/unit/util/suidmanager_spec.rb index 5962e4362..f8ba883f5 100755 --- a/spec/unit/util/suidmanager_spec.rb +++ b/spec/unit/util/suidmanager_spec.rb @@ -231,6 +231,13 @@ describe Puppet::Util::SUIDManager do output.first.should == 'output' output.last.should be_a(Process::Status) end + + it "should log a deprecation notice" do + Puppet::Util::Execution.stubs(:execute).returns("success") + Puppet.expects(:deprecation_warning).with('Puppet::Util::SUIDManager.run_and_capture is deprecated; please use Puppet::Util::Execution.execute instead.') + + output = Puppet::Util::SUIDManager.run_and_capture 'yay', user[:uid], user[:gid] + end end end diff --git a/spec/unit/util/tag_set_spec.rb b/spec/unit/util/tag_set_spec.rb new file mode 100644 index 000000000..c59674fcb --- /dev/null +++ b/spec/unit/util/tag_set_spec.rb @@ -0,0 +1,46 @@ +#! /usr/bin/env ruby +require 'spec_helper' + +require 'puppet/util/tag_set' + +RSpec::Matchers.define :be_one_of do |*expected| + match do |actual| + expected.include? actual + end + + failure_message_for_should do |actual| + "expected #{actual.inspect} to be one of #{expected.map(&:inspect).join(' or ')}" + end +end + +describe Puppet::Util::TagSet do + let(:set) { Puppet::Util::TagSet.new } + + it 'serializes to yaml as an array' do + array = ['a', :b, 1, 5.4] + set.merge(array) + + Set.new(YAML.load(set.to_yaml)).should == Set.new(array) + end + + it 'deserializes from a yaml array' do + array = ['a', :b, 1, 5.4] + + Puppet::Util::TagSet.from_yaml(array.to_yaml).should == Puppet::Util::TagSet.new(array) + end + + it 'round trips through pson' do + array = ['a', 'b', 1, 5.4] + set.merge(array) + + tes = Puppet::Util::TagSet.from_pson(PSON.parse(set.to_pson)) + tes.should == set + end + + it 'can join its elements with a string separator' do + array = ['a', 'b'] + set.merge(array) + + set.join(', ').should be_one_of('a, b', 'b, a') + end +end diff --git a/spec/unit/util/tagging_spec.rb b/spec/unit/util/tagging_spec.rb index 67b766fe0..248e915e9 100755 --- a/spec/unit/util/tagging_spec.rb +++ b/spec/unit/util/tagging_spec.rb @@ -3,92 +3,129 @@ require 'spec_helper' require 'puppet/util/tagging' -describe Puppet::Util::Tagging, "when adding tags" do - before do - @tagger = Object.new - @tagger.extend(Puppet::Util::Tagging) - end - - it "should have a method for adding tags" do - @tagger.should be_respond_to(:tag) - end - - it "should have a method for returning all tags" do - @tagger.should be_respond_to(:tags) - end +describe Puppet::Util::Tagging do + let(:tagger) { Object.new.extend(Puppet::Util::Tagging) } it "should add tags to the returned tag list" do - @tagger.tag("one") - @tagger.tags.should be_include("one") - end - - it "should not add duplicate tags to the returned tag list" do - @tagger.tag("one") - @tagger.tag("one") - @tagger.tags.should == ["one"] + tagger.tag("one") + expect(tagger.tags).to include("one") end it "should return a duplicate of the tag list, rather than the original" do - @tagger.tag("one") - tags = @tagger.tags + tagger.tag("one") + tags = tagger.tags tags << "two" - @tagger.tags.should_not be_include("two") + expect(tagger.tags).to_not include("two") end it "should add all provided tags to the tag list" do - @tagger.tag("one", "two") - @tagger.tags.should be_include("one") - @tagger.tags.should be_include("two") + tagger.tag("one", "two") + expect(tagger.tags).to include("one") + expect(tagger.tags).to include("two") end it "should fail on tags containing '*' characters" do - expect { @tagger.tag("bad*tag") }.to raise_error(Puppet::ParseError) + expect { tagger.tag("bad*tag") }.to raise_error(Puppet::ParseError) end it "should fail on tags starting with '-' characters" do - expect { @tagger.tag("-badtag") }.to raise_error(Puppet::ParseError) + expect { tagger.tag("-badtag") }.to raise_error(Puppet::ParseError) end it "should fail on tags containing ' ' characters" do - expect { @tagger.tag("bad tag") }.to raise_error(Puppet::ParseError) + expect { tagger.tag("bad tag") }.to raise_error(Puppet::ParseError) end it "should allow alpha tags" do - expect { @tagger.tag("good_tag") }.to_not raise_error + expect { tagger.tag("good_tag") }.not_to raise_error end it "should allow tags containing '.' characters" do - expect { @tagger.tag("good.tag") }.to_not raise_error + expect { tagger.tag("good.tag") }.to_not raise_error(Puppet::ParseError) end it "should add qualified classes as tags" do - @tagger.tag("one::two") - @tagger.tags.should be_include("one::two") + tagger.tag("one::two") + expect(tagger.tags).to include("one::two") end it "should add each part of qualified classes as tags" do - @tagger.tag("one::two::three") - @tagger.tags.should be_include("one") - @tagger.tags.should be_include("two") - @tagger.tags.should be_include("three") + tagger.tag("one::two::three") + expect(tagger.tags).to include('one') + expect(tagger.tags).to include("two") + expect(tagger.tags).to include("three") end it "should indicate when the object is tagged with a provided tag" do - @tagger.tag("one") - @tagger.should be_tagged("one") + tagger.tag("one") + expect(tagger).to be_tagged("one") end it "should indicate when the object is not tagged with a provided tag" do - @tagger.should_not be_tagged("one") + expect(tagger).to_not be_tagged("one") end it "should indicate when the object is tagged with any tag in an array" do - @tagger.tag("one") - @tagger.should be_tagged("one","two","three") + tagger.tag("one") + expect(tagger).to be_tagged("one","two","three") end it "should indicate when the object is not tagged with any tag in an array" do - @tagger.tag("one") - @tagger.should_not be_tagged("two","three") + tagger.tag("one") + expect(tagger).to_not be_tagged("two","three") + end + + context "when tagging" do + it "converts symbols to strings" do + tagger.tag(:hello) + expect(tagger.tags).to include('hello') + end + + it "downcases tags" do + tagger.tag(:HEllO) + tagger.tag("GooDByE") + expect(tagger).to be_tagged("hello") + expect(tagger).to be_tagged("goodbye") + end + + it "accepts hyphenated tags" do + tagger.tag("my-tag") + expect(tagger).to be_tagged("my-tag") + end + end + + context "when querying if tagged" do + it "responds true if queried on the entire set" do + tagger.tag("one", "two") + expect(tagger).to be_tagged("one", "two") + end + + it "responds true if queried on a subset" do + tagger.tag("one", "two", "three") + expect(tagger).to be_tagged("two", "one") + end + + it "responds true if queried on an overlapping but not fully contained set" do + tagger.tag("one", "two") + expect(tagger).to be_tagged("zero", "one") + end + + it "responds false if queried on a disjoint set" do + tagger.tag("one", "two", "three") + expect(tagger).to_not be_tagged("five") + end + + it "responds false if queried on the empty set" do + expect(tagger).to_not be_tagged + end + end + + context "when assigning tags" do + it "splits a string on ','" do + tagger.tags = "one, two, three" + expect(tagger).to be_tagged("one") + expect(tagger).to be_tagged("two") + expect(tagger).to be_tagged("three") + end end end diff --git a/spec/unit/util/watcher_spec.rb b/spec/unit/util/watcher_spec.rb index 64fab9681..33f75ab12 100644 --- a/spec/unit/util/watcher_spec.rb +++ b/spec/unit/util/watcher_spec.rb @@ -14,7 +14,10 @@ describe Puppet::Util::Watcher do let(:filename) { "fake" } def after_reading_the_sequence(initial, *results) - expectation = File.stubs(:stat).with(filename) + mock_file = mock(filename) + Puppet::FileSystem::File.expects(:new).with(filename).at_least(1).returns mock_file + + expectation = mock_file.stubs(:stat) ([initial] + results).each do |result| expectation = if result.is_a? Class expectation.raises(result) diff --git a/spec/unit/util/windows/access_control_entry_spec.rb b/spec/unit/util/windows/access_control_entry_spec.rb new file mode 100644 index 000000000..b139b0d42 --- /dev/null +++ b/spec/unit/util/windows/access_control_entry_spec.rb @@ -0,0 +1,67 @@ +#!/usr/bin/env ruby +require 'spec_helper' +require 'puppet/util/windows' + +describe "Puppet::Util::Windows::AccessControlEntry", :if => Puppet.features.microsoft_windows? do + let(:klass) { Puppet::Util::Windows::AccessControlEntry } + let(:sid) { 'S-1-5-18' } + let(:mask) { Windows::File::FILE_ALL_ACCESS } + + it "creates an access allowed ace" do + ace = klass.new(sid, mask) + + ace.type.should == klass::ACCESS_ALLOWED_ACE_TYPE + end + + it "creates an access denied ace" do + ace = klass.new(sid, mask, 0, klass::ACCESS_DENIED_ACE_TYPE) + + ace.type.should == klass::ACCESS_DENIED_ACE_TYPE + end + + it "creates a non-inherited ace by default" do + ace = klass.new(sid, mask) + + ace.should_not be_inherited + end + + it "creates an inherited ace" do + ace = klass.new(sid, mask, klass::INHERITED_ACE) + + ace.should be_inherited + end + + it "creates a non-inherit-only ace by default" do + ace = klass.new(sid, mask) + + ace.should_not be_inherit_only + end + + it "creates an inherit-only ace" do + ace = klass.new(sid, mask, klass::INHERIT_ONLY_ACE) + + ace.should be_inherit_only + end + + context "when comparing aces" do + let(:ace1) { klass.new(sid, mask, klass::INHERIT_ONLY_ACE, klass::ACCESS_DENIED_ACE_TYPE) } + let(:ace2) { klass.new(sid, mask, klass::INHERIT_ONLY_ACE, klass::ACCESS_DENIED_ACE_TYPE) } + + it "returns true if different objects have the same set of values" do + ace1.should == ace2 + end + + it "returns false if different objects have different sets of values" do + ace = klass.new(sid, mask) + ace.should_not == ace1 + end + + it "returns true when testing if two objects are eql?" do + ace1.eql?(ace2) + end + + it "returns false when comparing object identity" do + ace1.should_not be_equal(ace2) + end + end +end diff --git a/spec/unit/util/windows/access_control_list_spec.rb b/spec/unit/util/windows/access_control_list_spec.rb new file mode 100644 index 000000000..66c917b29 --- /dev/null +++ b/spec/unit/util/windows/access_control_list_spec.rb @@ -0,0 +1,133 @@ +#!/usr/bin/env ruby +require 'spec_helper' +require 'puppet/util/windows' + +describe "Puppet::Util::Windows::AccessControlList", :if => Puppet.features.microsoft_windows? do + let(:klass) { Puppet::Util::Windows::AccessControlList } + let(:system_sid) { 'S-1-5-18' } + let(:admins_sid) { 'S-1-5-544' } + let(:none_sid) { 'S-1-0-0' } + + let(:system_ace) do + Puppet::Util::Windows::AccessControlEntry.new(system_sid, 0x1) + end + let(:admins_ace) do + Puppet::Util::Windows::AccessControlEntry.new(admins_sid, 0x2) + end + let(:none_ace) do + Puppet::Util::Windows::AccessControlEntry.new(none_sid, 0x3) + end + + it "constructs an empty list" do + acl = klass.new + + acl.to_a.should be_empty + end + + it "supports copy constructor" do + aces = klass.new([system_ace]).to_a + + aces.to_a.should == [system_ace] + end + + context "appending" do + it "appends an allow ace" do + acl = klass.new + acl.allow(system_sid, 0x1, 0x2) + + acl.first.type.should == klass::ACCESS_ALLOWED_ACE_TYPE + end + + it "appends a deny ace" do + acl = klass.new + acl.deny(system_sid, 0x1, 0x2) + + acl.first.type.should == klass::ACCESS_DENIED_ACE_TYPE + end + + it "always appends, never overwrites an ACE" do + acl = klass.new([system_ace]) + acl.allow(admins_sid, admins_ace.mask, admins_ace.flags) + + aces = acl.to_a + aces.size.should == 2 + aces[0].should == system_ace + aces[1].sid.should == admins_sid + aces[1].mask.should == admins_ace.mask + aces[1].flags.should == admins_ace.flags + end + end + + context "reassigning" do + it "preserves the mask from the old sid when reassigning to the new sid" do + dacl = klass.new([system_ace]) + + dacl.reassign!(system_ace.sid, admins_ace.sid) + # we removed system, so ignore prepended ace + ace = dacl.to_a[1] + ace.sid.should == admins_sid + ace.mask.should == system_ace.mask + end + + it "matches multiple sids" do + dacl = klass.new([system_ace, system_ace]) + + dacl.reassign!(system_ace.sid, admins_ace.sid) + # we removed system, so ignore prepended ace + aces = dacl.to_a + aces.size.should == 3 + aces.to_a[1,2].each do |ace| + ace.sid.should == admins_ace.sid + end + end + + it "preserves aces for sids that don't match, in their original order" do + dacl = klass.new([system_ace, admins_ace]) + + dacl.reassign!(system_sid, none_sid) + aces = dacl.to_a + aces[1].sid == admins_ace.sid + end + + it "preserves inherited aces, even if the sids match" do + flags = Puppet::Util::Windows::AccessControlEntry::INHERITED_ACE + inherited_ace = Puppet::Util::Windows::AccessControlEntry.new(system_sid, 0x1, flags) + dacl = klass.new([inherited_ace, system_ace]) + dacl.reassign!(system_sid, none_sid) + aces = dacl.to_a + + aces[0].sid.should == system_sid + end + + it "prepends an explicit ace for the new sid with the same mask and basic inheritance as the inherited ace" do + expected_flags = + Puppet::Util::Windows::AccessControlEntry::OBJECT_INHERIT_ACE | + Puppet::Util::Windows::AccessControlEntry::CONTAINER_INHERIT_ACE | + Puppet::Util::Windows::AccessControlEntry::INHERIT_ONLY_ACE + + flags = Puppet::Util::Windows::AccessControlEntry::INHERITED_ACE | expected_flags + + inherited_ace = Puppet::Util::Windows::AccessControlEntry.new(system_sid, 0x1, flags) + dacl = klass.new([inherited_ace]) + dacl.reassign!(system_sid, none_sid) + aces = dacl.to_a + + aces.size.should == 2 + aces[0].sid.should == none_sid + aces[0].should_not be_inherited + aces[0].flags.should == expected_flags + + aces[1].sid.should == system_sid + aces[1].should be_inherited + end + + it "makes a copy of the ace prior to modifying it" do + arr = [system_ace] + + acl = klass.new(arr) + acl.reassign!(system_sid, none_sid) + + arr[0].sid.should == system_sid + end + end +end diff --git a/spec/unit/util/windows/root_certs_spec.rb b/spec/unit/util/windows/root_certs_spec.rb index 155707e4e..6b1d9891e 100755 --- a/spec/unit/util/windows/root_certs_spec.rb +++ b/spec/unit/util/windows/root_certs_spec.rb @@ -3,13 +3,15 @@ require 'spec_helper' require 'puppet/util/windows' describe "Puppet::Util::Windows::RootCerts", :if => Puppet::Util::Platform.windows? do - let(:klass) { Puppet::Util::Windows::RootCerts } - let(:x509) { 'mycert' } - - context '#each' do - it "should enumerate each root cert" do - klass.expects(:load_certs).returns([x509]) - klass.instance.to_a.should == [x509] - end + let(:x509_store) { Puppet::Util::Windows::RootCerts.instance.to_a } + + it "should return at least one X509 certificate" do + expect(x509_store.to_a).to have_at_least(1).items + end + + it "should return an X509 certificate with a subject" do + x509 = x509_store.first + + expect(x509.subject.to_s).to match(/CN=.*/) end end diff --git a/spec/unit/util/windows/security_descriptor_spec.rb b/spec/unit/util/windows/security_descriptor_spec.rb new file mode 100644 index 000000000..61db2f598 --- /dev/null +++ b/spec/unit/util/windows/security_descriptor_spec.rb @@ -0,0 +1,117 @@ +#!/usr/bin/env ruby + +require 'spec_helper' +require 'puppet/util/windows' + +describe "Puppet::Util::Windows::SecurityDescriptor", :if => Puppet.features.microsoft_windows? do + let(:system_sid) { Win32::Security::SID::LocalSystem } + let(:admins_sid) { Win32::Security::SID::BuiltinAdministrators } + let(:group_sid) { Win32::Security::SID::Nobody } + let(:new_sid) { 'S-1-5-32-500-1-2-3' } + + def empty_dacl + Puppet::Util::Windows::AccessControlList.new + end + + def system_ace_dacl + dacl = Puppet::Util::Windows::AccessControlList.new + dacl.allow(system_sid, 0x1) + dacl + end + + context "owner" do + it "changes the owner" do + sd = Puppet::Util::Windows::SecurityDescriptor.new(system_sid, group_sid, system_ace_dacl) + sd.owner = new_sid + + sd.owner.should == new_sid + end + + it "performs a noop if the new owner is the same as the old one" do + dacl = system_ace_dacl + sd = Puppet::Util::Windows::SecurityDescriptor.new(system_sid, group_sid, dacl) + sd.owner = sd.owner + + sd.dacl.object_id.should == dacl.object_id + end + + it "prepends SYSTEM when security descriptor owner is no longer SYSTEM" do + sd = Puppet::Util::Windows::SecurityDescriptor.new(system_sid, group_sid, system_ace_dacl) + sd.owner = new_sid + + aces = sd.dacl.to_a + aces.size.should == 2 + aces[0].sid.should == system_sid + aces[1].sid.should == new_sid + end + + it "does not prepend SYSTEM when DACL already contains inherited SYSTEM ace" do + sd = Puppet::Util::Windows::SecurityDescriptor.new(admins_sid, system_sid, empty_dacl) + sd.dacl.allow(admins_sid, 0x1) + sd.dacl.allow(system_sid, 0x1, Puppet::Util::Windows::AccessControlEntry::INHERITED_ACE) + sd.owner = new_sid + + aces = sd.dacl.to_a + aces.size.should == 2 + aces[0].sid.should == new_sid + end + + it "does not prepend SYSTEM when security descriptor owner wasn't SYSTEM" do + sd = Puppet::Util::Windows::SecurityDescriptor.new(group_sid, group_sid, empty_dacl) + sd.dacl.allow(group_sid, 0x1) + sd.owner = new_sid + + aces = sd.dacl.to_a + aces.size.should == 1 + aces[0].sid.should == new_sid + end + end + + context "group" do + it "changes the group" do + sd = Puppet::Util::Windows::SecurityDescriptor.new(system_sid, group_sid, system_ace_dacl) + sd.group = new_sid + + sd.group.should == new_sid + end + + it "performs a noop if the new group is the same as the old one" do + dacl = system_ace_dacl + sd = Puppet::Util::Windows::SecurityDescriptor.new(system_sid, group_sid, dacl) + sd.group = sd.group + + sd.dacl.object_id.should == dacl.object_id + end + + it "prepends SYSTEM when security descriptor group is no longer SYSTEM" do + sd = Puppet::Util::Windows::SecurityDescriptor.new(new_sid, system_sid, system_ace_dacl) + sd.group = new_sid + + aces = sd.dacl.to_a + aces.size.should == 2 + aces[0].sid.should == system_sid + aces[1].sid.should == new_sid + end + + it "does not prepend SYSTEM when DACL already contains inherited SYSTEM ace" do + sd = Puppet::Util::Windows::SecurityDescriptor.new(admins_sid, admins_sid, empty_dacl) + sd.dacl.allow(admins_sid, 0x1) + sd.dacl.allow(system_sid, 0x1, Puppet::Util::Windows::AccessControlEntry::INHERITED_ACE) + sd.group = new_sid + + aces = sd.dacl.to_a + aces.size.should == 2 + aces[0].sid.should == new_sid + end + + it "does not prepend SYSTEM when security descriptor group wasn't SYSTEM" do + sd = Puppet::Util::Windows::SecurityDescriptor.new(group_sid, group_sid, empty_dacl) + sd.dacl.allow(group_sid, 0x1) + sd.group = new_sid + + aces = sd.dacl.to_a + aces.size.should == 1 + aces[0].sid.should == new_sid + end + end +end diff --git a/spec/unit/util/windows/sid_spec.rb b/spec/unit/util/windows/sid_spec.rb index d53e93ae6..770512188 100755 --- a/spec/unit/util/windows/sid_spec.rb +++ b/spec/unit/util/windows/sid_spec.rb @@ -15,6 +15,45 @@ describe "Puppet::Util::Windows::SID", :if => Puppet.features.microsoft_windows? 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(Win32::Security::SID::Error, /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 @@ -28,6 +67,10 @@ describe "Puppet::Util::Windows::SID", :if => Puppet.features.microsoft_windows? 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 @@ -37,6 +80,32 @@ describe "Puppet::Util::Windows::SID", :if => Puppet.features.microsoft_windows? 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 diff --git a/spec/unit/util/yaml_spec.rb b/spec/unit/util/yaml_spec.rb index 960388ade..978afb0a2 100644 --- a/spec/unit/util/yaml_spec.rb +++ b/spec/unit/util/yaml_spec.rb @@ -33,6 +33,20 @@ describe Puppet::Util::Yaml 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::File.new(filename).touch + + expect(Puppet::Util::Yaml.load_file(filename)).to be_false + end + + it "allows return value to be overridden" do + Puppet::FileSystem::File.new(filename).touch + + 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) diff --git a/spec/unit/util_spec.rb b/spec/unit/util_spec.rb index 48b6e6fc5..58cc4bbd8 100755 --- a/spec/unit/util_spec.rb +++ b/spec/unit/util_spec.rb @@ -19,7 +19,7 @@ describe Puppet::Util do end def get_mode(file) - File.lstat(file).mode & 07777 + Puppet::FileSystem::File.new(file).lstat.mode & 07777 end end @@ -449,7 +449,7 @@ describe Puppet::Util do 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 = File.stat(target.path).ino + inode = Puppet::FileSystem::File.new(target.path).stat.ino yielded = false subject.replace_file(target.path, 0600) do |fh| @@ -458,19 +458,19 @@ describe Puppet::Util do end yielded.should be_true - File.stat(target.path).ino.should_not == inode + Puppet::FileSystem::File.new(target.path).stat.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' - File.should_not be_exist(new_target) + Puppet::FileSystem::File.exist?(new_target).should be_false begin subject.replace_file(new_target, 0555) {|fh| fh.puts "foo" } get_mode(new_target).should == 0555 ensure - File.unlink(new_target) if File.exists?(new_target) + Puppet::FileSystem::File.unlink(new_target) if Puppet::FileSystem::File.exist?(new_target) end end @@ -502,14 +502,14 @@ describe Puppet::Util do {: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" - File.should_not be_exist(new_target) + Puppet::FileSystem::File.exist?(new_target).should be_false begin subject.replace_file(new_target, mode) {|fh| fh.puts "this is an interesting content" } get_mode(new_target).should == 0664 ensure - File.unlink(new_target) if File.exists?(new_target) + Puppet::FileSystem::File.unlink(new_target) if Puppet::FileSystem::File.exist?(new_target) end end end |
