diff options
author | Stig Sandbeck Mathisen <ssm@debian.org> | 2014-09-07 10:14:36 +0200 |
---|---|---|
committer | Stig Sandbeck Mathisen <ssm@debian.org> | 2014-09-07 10:14:36 +0200 |
commit | d4b83be375ac1dead058e091191ee7c7b7c24c8a (patch) | |
tree | dc825687392ae3068de5b764be60c53122d9e02a /spec/unit | |
parent | 229cbb976fe0f70f5f30548b83517b415840f9bb (diff) | |
parent | 1681684857c6e39d60d87b0b3520d8783977ceff (diff) | |
download | puppet-upstream/3.7.0.tar.gz |
Imported Upstream version 3.7.0upstream/3.7.0
Diffstat (limited to 'spec/unit')
183 files changed, 6409 insertions, 3644 deletions
diff --git a/spec/unit/application/apply_spec.rb b/spec/unit/application/apply_spec.rb index de4784fbe..ec6afee66 100755 --- a/spec/unit/application/apply_spec.rb +++ b/spec/unit/application/apply_spec.rb @@ -131,7 +131,9 @@ describe Puppet::Application::Apply do @apply.setup - expect(Puppet::Util::Profiler.current).to be_a(Puppet::Util::Profiler::WallClock) + expect(Puppet::Util::Profiler.current).to satisfy do |ps| + ps.any? {|p| p.is_a? Puppet::Util::Profiler::WallClock } + end end it "does not have a profiler if profiling is disabled" do @@ -139,7 +141,7 @@ describe Puppet::Application::Apply do @apply.setup - expect(Puppet::Util::Profiler.current).to eq(Puppet::Util::Profiler::NONE) + expect(Puppet::Util::Profiler.current.length).to be 0 end it "should set default_file_terminus to `file_server` to be local" do diff --git a/spec/unit/application/doc_spec.rb b/spec/unit/application/doc_spec.rb index 8e43907ca..2f8624abd 100755 --- a/spec/unit/application/doc_spec.rb +++ b/spec/unit/application/doc_spec.rb @@ -259,6 +259,7 @@ describe Puppet::Application::Doc do let(:envdir) { tmpdir('env') } let(:modules) { File.join(envdir, "modules") } + let(:modules2) { File.join(envdir, "modules2") } let(:manifests) { File.join(envdir, "manifests") } before :each do @@ -277,8 +278,8 @@ describe Puppet::Application::Doc do around(:each) do |example| FileUtils.mkdir_p(modules) - @env = Puppet::Node::Environment.create(Puppet[:environment].to_sym, [modules], "#{manifests}/site.pp") - Puppet.override(:environments => Puppet::Environments::Static.new(@env)) do + env = Puppet::Node::Environment.create(Puppet[:environment].to_sym, [modules], "#{manifests}/site.pp") + Puppet.override({:environments => Puppet::Environments::Static.new(env), :current_environment => env}) do example.run end end @@ -287,39 +288,42 @@ describe Puppet::Application::Doc do @doc.options[:all] = true Puppet.settings.expects(:[]=).with(:document_all, true) - expect { @doc.rdoc }.to exit_with 0 + expect { @doc.rdoc }.to exit_with(0) end it "should call Puppet::Util::RDoc.rdoc in full mode" do Puppet::Util::RDoc.expects(:rdoc).with('doc', [modules, manifests], nil) - expect { @doc.rdoc }.to exit_with 0 + expect { @doc.rdoc }.to exit_with(0) end it "should call Puppet::Util::RDoc.rdoc with a charset if --charset has been provided" do @doc.options[:charset] = 'utf-8' Puppet::Util::RDoc.expects(:rdoc).with('doc', [modules, manifests], "utf-8") - expect { @doc.rdoc }.to exit_with 0 + expect { @doc.rdoc }.to exit_with(0) end it "should call Puppet::Util::RDoc.rdoc in full mode with outputdir set to doc if no --outputdir" do @doc.options[:outputdir] = false Puppet::Util::RDoc.expects(:rdoc).with('doc', [modules, manifests], nil) - expect { @doc.rdoc }.to exit_with 0 + expect { @doc.rdoc }.to exit_with(0) end it "should call Puppet::Util::RDoc.manifestdoc in manifest mode" do @doc.manifest = true Puppet::Util::RDoc.expects(:manifestdoc) - expect { @doc.rdoc }.to exit_with 0 + expect { @doc.rdoc }.to exit_with(0) end it "should get modulepath and manifestdir values from the environment" do - @env.expects(:modulepath).returns(['envmodules1','envmodules2']) - @env.expects(:manifest).returns('envmanifests/site.pp') - - Puppet::Util::RDoc.expects(:rdoc).with('doc', ['envmodules1','envmodules2','envmanifests'], nil) - - expect { @doc.rdoc }.to exit_with 0 + FileUtils.mkdir_p(modules) + FileUtils.mkdir_p(modules2) + env = Puppet::Node::Environment.create(Puppet[:environment].to_sym, + [modules, modules2], + "envmanifests/site.pp") + Puppet.override({:environments => Puppet::Environments::Static.new(env), :current_environment => env}) do + Puppet::Util::RDoc.stubs(:rdoc).with('doc', [modules.to_s, modules2.to_s, env.manifest.to_s], nil) + expect { @doc.rdoc }.to exit_with(0) + end end end diff --git a/spec/unit/application/master_spec.rb b/spec/unit/application/master_spec.rb index 4d3167ce8..ef9c1b174 100755 --- a/spec/unit/application/master_spec.rb +++ b/spec/unit/application/master_spec.rb @@ -114,38 +114,46 @@ describe Puppet::Application::Master, :unless => Puppet.features.microsoft_windo expect { @master.setup }.to raise_error(Puppet::Error, /Puppet master is not supported on Microsoft Windows/) end - it "should set log level to debug if --debug was passed" do - @master.options.stubs(:[]).with(:debug).returns(true) - @master.setup - Puppet::Log.level.should == :debug - end - - it "should set log level to info if --verbose was passed" do - @master.options.stubs(:[]).with(:verbose).returns(true) - @master.setup - Puppet::Log.level.should == :info - end - - it "should set console as the log destination if no --logdest and --daemonize" do - @master.stubs(:[]).with(:daemonize).returns(:false) - - Puppet::Log.expects(:newdestination).with(:syslog) - - @master.setup - end + describe "setting up logging" do + it "sets the log level" do + @master.expects(:set_log_level) + @master.setup + end - it "should set syslog as the log destination if no --logdest and not --daemonize" do - Puppet::Log.expects(:newdestination).with(:syslog) + describe "when the log destination is not explicitly configured" do + before do + @master.options.stubs(:[]).with(:setdest).returns false + end - @master.setup - end + it "logs to the console when --compile is given" do + @master.options.stubs(:[]).with(:node).returns "default" + Puppet::Util::Log.expects(:newdestination).with(:console) + @master.setup + end - it "should set syslog as the log destination if --rack" do - @master.options.stubs(:[]).with(:rack).returns(:true) + it "logs to the console when the master is not daemonized or run with rack" do + Puppet::Util::Log.expects(:newdestination).with(:console) + Puppet[:daemonize] = false + @master.options.stubs(:[]).with(:rack).returns(false) + @master.setup + end - Puppet::Log.expects(:newdestination).with(:syslog) + it "logs to syslog when the master is daemonized" do + Puppet::Util::Log.expects(:newdestination).with(:console).never + Puppet::Util::Log.expects(:newdestination).with(:syslog) + Puppet[:daemonize] = true + @master.options.stubs(:[]).with(:rack).returns(false) + @master.setup + end - @master.setup + it "logs to syslog when the master is run with rack" do + Puppet::Util::Log.expects(:newdestination).with(:console).never + Puppet::Util::Log.expects(:newdestination).with(:syslog) + Puppet[:daemonize] = false + @master.options.stubs(:[]).with(:rack).returns(true) + @master.setup + end + end end it "should print puppet config if asked to in Puppet config" do diff --git a/spec/unit/application/resource_spec.rb b/spec/unit/application/resource_spec.rb index f2e2596ca..029aa466d 100755 --- a/spec/unit/application/resource_spec.rb +++ b/spec/unit/application/resource_spec.rb @@ -16,11 +16,6 @@ describe Puppet::Application::Resource do @resource_app.preinit @resource_app.extra_params.should == [] end - - it "should load Facter facts" do - Facter.expects(:loadfacts).once - @resource_app.preinit - end end describe "when handling options" do diff --git a/spec/unit/configurer/downloader_factory_spec.rb b/spec/unit/configurer/downloader_factory_spec.rb new file mode 100755 index 000000000..fae919918 --- /dev/null +++ b/spec/unit/configurer/downloader_factory_spec.rb @@ -0,0 +1,96 @@ +#! /usr/bin/env ruby +require 'spec_helper' +require 'puppet/configurer' + +describe Puppet::Configurer::DownloaderFactory do + let(:factory) { Puppet::Configurer::DownloaderFactory.new } + let(:environment) { Puppet::Node::Environment.create(:myenv, []) } + + let(:plugin_downloader) do + factory.create_plugin_downloader(environment) + end + + let(:facts_downloader) do + factory.create_plugin_facts_downloader(environment) + end + + def ignores_source_permissions(downloader) + expect(downloader.file[:source_permissions]).to eq(:ignore) + end + + def uses_source_permissions(downloader) + expect(downloader.file[:source_permissions]).to eq(:use) + end + + context "when creating a plugin downloader for modules" do + it 'is named "plugin"' do + expect(plugin_downloader.name).to eq('plugin') + end + + it 'downloads files into Puppet[:plugindest]' do + plugindest = File.expand_path("/tmp/pdest") + Puppet[:plugindest] = plugindest + + expect(plugin_downloader.file[:path]).to eq(plugindest) + end + + it 'downloads files from Puppet[:pluginsource]' do + Puppet[:pluginsource] = 'puppet:///myotherplugins' + + expect(plugin_downloader.file[:source]).to eq([Puppet[:pluginsource]]) + end + + it 'ignores files from Puppet[:pluginsignore]' do + Puppet[:pluginsignore] = 'pignore' + + expect(plugin_downloader.file[:ignore]).to eq(['pignore']) + end + + it 'splits Puppet[:pluginsignore] on whitespace' do + Puppet[:pluginsignore] = ".svn CVS .git" + + expect(plugin_downloader.file[:ignore]).to eq(%w[.svn CVS .git]) + end + + it "ignores source permissions" do + ignores_source_permissions(plugin_downloader) + end + end + + context "when creating a plugin downloader for external facts" do + it 'is named "pluginfacts"' do + expect(facts_downloader.name).to eq('pluginfacts') + end + + it 'downloads files into Puppet[:pluginfactdest]' do + plugindest = File.expand_path("/tmp/pdest") + Puppet[:pluginfactdest] = plugindest + + expect(facts_downloader.file[:path]).to eq(plugindest) + end + + it 'downloads files from Puppet[:pluginfactsource]' do + Puppet[:pluginfactsource] = 'puppet:///myotherfacts' + + expect(facts_downloader.file[:source]).to eq([Puppet[:pluginfactsource]]) + end + + it 'ignores files from Puppet[:pluginsignore]' do + Puppet[:pluginsignore] = 'pignore' + + expect(facts_downloader.file[:ignore]).to eq(['pignore']) + end + + context "on POSIX", :if => Puppet.features.posix? do + it "uses source permissions" do + uses_source_permissions(facts_downloader) + end + end + + context "on Windows", :if => Puppet.features.microsoft_windows? do + it "ignores source permissions during external fact pluginsync" do + ignores_source_permissions(facts_downloader) + end + end + end +end diff --git a/spec/unit/configurer/downloader_spec.rb b/spec/unit/configurer/downloader_spec.rb index 9e1d3a29b..71b88bb51 100755 --- a/spec/unit/configurer/downloader_spec.rb +++ b/spec/unit/configurer/downloader_spec.rb @@ -6,6 +6,10 @@ require 'puppet/configurer/downloader' describe Puppet::Configurer::Downloader do require 'puppet_spec/files' include PuppetSpec::Files + + let(:path) { Puppet[:plugindest] } + let(:source) { 'puppet://puppet/plugins' } + it "should require a name" do lambda { Puppet::Configurer::Downloader.new }.should raise_error(ArgumentError) end @@ -21,87 +25,115 @@ describe Puppet::Configurer::Downloader do dler.source.should == "source" end - describe "when creating the file that does the downloading" do - before do - @dler = Puppet::Configurer::Downloader.new("foo", "path", "source") - end + def downloader(options = {}) + options[:name] ||= "facts" + options[:path] ||= path + options[:source_permissions] ||= :ignore + Puppet::Configurer::Downloader.new(options[:name], options[:path], source, options[:ignore], options[:environment], options[:source_permissions]) + end + + def generate_file_resource(options = {}) + dler = downloader(options) + dler.file + end + describe "when creating the file that does the downloading" do it "should create a file instance with the right path and source" do - Puppet::Type.type(:file).expects(:new).with { |opts| opts[:path] == "path" and opts[:source] == "source" } - @dler.file + file = generate_file_resource(:path => path, :source => source) + + expect(file[:path]).to eq(path) + expect(file[:source]).to eq([source]) end it "should tag the file with the downloader name" do - Puppet::Type.type(:file).expects(:new).with { |opts| opts[:tag] == "foo" } - @dler.file + name = "mydownloader" + file = generate_file_resource(:name => name) + + expect(file[:tag]).to eq([name]) end it "should always recurse" do - Puppet::Type.type(:file).expects(:new).with { |opts| opts[:recurse] == true } - @dler.file + file = generate_file_resource + + expect(file[:recurse]).to be_true end it "should always purge" do - Puppet::Type.type(:file).expects(:new).with { |opts| opts[:purge] == true } - @dler.file + file = generate_file_resource + + expect(file[:purge]).to be_true end it "should never be in noop" do - Puppet::Type.type(:file).expects(:new).with { |opts| opts[:noop] == false } - @dler.file + file = generate_file_resource + + expect(file[:noop]).to be_false + end + + it "should set source_permissions to ignore by default" do + file = generate_file_resource + + expect(file[:source_permissions]).to eq(:ignore) end - it "should set source_permissions to ignore" do - Puppet::Type.type(:file).expects(:new).with { |opts| opts[:source_permissions] == :ignore } - @dler.file + it "should allow source_permissions to be overridden" do + file = generate_file_resource(:source_permissions => :use) + + expect(file[:source_permissions]).to eq(:use) end describe "on POSIX", :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 } - @dler.file + + file = generate_file_resource(:path => '/path') + expect(file[:owner]).to eq(51) end it "should always set the group to the current GID" do Process.expects(:gid).returns 61 - Puppet::Type.type(:file).expects(:new).with { |opts| opts[:group] == 61 } - @dler.file + + file = generate_file_resource(:path => '/path') + expect(file[:group]).to eq(61) end end 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 + file = generate_file_resource(:path => 'C:/path') + + expect(file[:owner]).to be_nil end it "should omit the group" do - Puppet::Type.type(:file).expects(:new).with { |opts| opts[:group] == nil } - @dler.file + file = generate_file_resource(:path => 'C:/path') + + expect(file[:group]).to be_nil end end it "should always force the download" do - Puppet::Type.type(:file).expects(:new).with { |opts| opts[:force] == true } - @dler.file + file = generate_file_resource + + expect(file[:force]).to be_true end it "should never back up when downloading" do - Puppet::Type.type(:file).expects(:new).with { |opts| opts[:backup] == false } - @dler.file + file = generate_file_resource + + expect(file[:backup]).to be_false end it "should support providing an 'ignore' parameter" do - Puppet::Type.type(:file).expects(:new).with { |opts| opts[:ignore] == [".svn"] } - @dler = Puppet::Configurer::Downloader.new("foo", "path", "source", ".svn") - @dler.file + file = generate_file_resource(:ignore => '.svn') + + expect(file[:ignore]).to eq(['.svn']) end it "should split the 'ignore' parameter on whitespace" do - Puppet::Type.type(:file).expects(:new).with { |opts| opts[:ignore] == %w{.svn CVS} } - @dler = Puppet::Configurer::Downloader.new("foo", "path", "source", ".svn CVS") - @dler.file + file = generate_file_resource(:ignore => '.svn CVS') + + expect(file[:ignore]).to eq(['.svn', 'CVS']) end end @@ -142,25 +174,6 @@ describe Puppet::Configurer::Downloader do it "should log that it is downloading" do Puppet.expects(:info) - Timeout.stubs(:timeout) - - @dler.evaluate - end - - it "should set a timeout for the download using the `configtimeout` setting" do - Puppet[:configtimeout] = 50 - Timeout.expects(:timeout).with(50) - - @dler.evaluate - end - - it "should apply the catalog within the timeout block" do - catalog = mock 'catalog' - @dler.expects(:catalog).returns(catalog) - - Timeout.expects(:timeout).yields - - catalog.expects(:apply) @dler.evaluate end @@ -172,8 +185,6 @@ describe Puppet::Configurer::Downloader do @dler.expects(:catalog).returns(catalog) catalog.expects(:apply).yields(trans) - Timeout.expects(:timeout).yields - resource = mock 'resource' resource.expects(:[]).with(:path).returns "/changed/file" @@ -189,8 +200,6 @@ describe Puppet::Configurer::Downloader do @dler.expects(:catalog).returns(catalog) catalog.expects(:apply).yields(trans) - Timeout.expects(:timeout).yields - resource = mock 'resource' resource.expects(:[]).with(:path).returns "/changed/file" @@ -203,7 +212,9 @@ describe Puppet::Configurer::Downloader do it "should catch and log exceptions" do Puppet.expects(:err) - Timeout.stubs(:timeout).raises(Puppet::Error, "testing") + # The downloader creates a new catalog for each apply, and really the only object + # that it is possible to stub for the purpose of generating a puppet error + Puppet::Resource::Catalog.any_instance.stubs(:apply).raises(Puppet::Error, "testing") lambda { @dler.evaluate }.should_not raise_error end diff --git a/spec/unit/configurer/plugin_handler_spec.rb b/spec/unit/configurer/plugin_handler_spec.rb index 295629481..a80236888 100755 --- a/spec/unit/configurer/plugin_handler_spec.rb +++ b/spec/unit/configurer/plugin_handler_spec.rb @@ -3,37 +3,37 @@ require 'spec_helper' require 'puppet/configurer' require 'puppet/configurer/plugin_handler' -class PluginHandlerTester - include Puppet::Configurer::PluginHandler - attr_accessor :environment -end - describe Puppet::Configurer::PluginHandler do - before do - @pluginhandler = PluginHandlerTester.new + let(:factory) { Puppet::Configurer::DownloaderFactory.new } + let(:pluginhandler) { Puppet::Configurer::PluginHandler.new(factory) } + let(:environment) { Puppet::Node::Environment.create(:myenv, []) } + before :each do # PluginHandler#load_plugin has an extra-strong rescue clause # this mock is to make sure that we don't silently ignore errors Puppet.expects(:err).never end - it "should use an Agent Downloader, with the name, source, destination, ignore, and environment set correctly, to download plugins when downloading is enabled" do - environment = Puppet::Node::Environment.create(:myenv, []) - Puppet.features.stubs(:external_facts?).returns(:true) - plugindest = File.expand_path("/tmp/pdest") - Puppet[:pluginsource] = "psource" - Puppet[:plugindest] = plugindest - Puppet[:pluginsignore] = "pignore" - Puppet[:pluginfactsource] = "psource" - Puppet[:pluginfactdest] = plugindest + it "downloads plugins and facts" do + Puppet.features.stubs(:external_facts?).returns(true) + + plugin_downloader = stub('plugin-downloader', :evaluate => []) + facts_downloader = stub('facts-downloader', :evaluate => []) + + factory.expects(:create_plugin_downloader).returns(plugin_downloader) + factory.expects(:create_plugin_facts_downloader).returns(facts_downloader) + + pluginhandler.download_plugins(environment) + end + + it "skips facts if not enabled" do + Puppet.features.stubs(:external_facts?).returns(false) - downloader = mock 'downloader' - Puppet::Configurer::Downloader.expects(:new).with("pluginfacts", plugindest, "psource", "pignore", environment).returns downloader - Puppet::Configurer::Downloader.expects(:new).with("plugin", plugindest, "psource", "pignore", environment).returns downloader + plugin_downloader = stub('plugin-downloader', :evaluate => []) - downloader.stubs(:evaluate).returns([]) - downloader.expects(:evaluate).twice + factory.expects(:create_plugin_downloader).returns(plugin_downloader) + factory.expects(:create_plugin_facts_downloader).never - @pluginhandler.download_plugins(environment) + pluginhandler.download_plugins(environment) end end diff --git a/spec/unit/configurer_spec.rb b/spec/unit/configurer_spec.rb index ecbedec28..093990591 100755 --- a/spec/unit/configurer_spec.rb +++ b/spec/unit/configurer_spec.rb @@ -12,10 +12,6 @@ describe Puppet::Configurer do Puppet[:report] = true end - it "should include the Plugin Handler module" do - Puppet::Configurer.ancestors.should be_include(Puppet::Configurer::PluginHandler) - end - it "should include the Fact Handler module" do Puppet::Configurer.ancestors.should be_include(Puppet::Configurer::FactHandler) end diff --git a/spec/unit/defaults_spec.rb b/spec/unit/defaults_spec.rb index f86283a64..0d141d91c 100644 --- a/spec/unit/defaults_spec.rb +++ b/spec/unit/defaults_spec.rb @@ -41,4 +41,34 @@ describe "Defaults" do end end end + + describe 'cfacter' do + + before :each do + Facter.reset + end + + it 'should default to false' do + Puppet.settings[:cfacter].should be_false + end + + it 'should raise an error if cfacter is not installed' do + Puppet.features.stubs(:cfacter?).returns false + lambda { Puppet.settings[:cfacter] = true }.should raise_exception ArgumentError, 'cfacter version 0.2.0 or later is not installed.' + end + + it 'should raise an error if facter has already evaluated facts' do + Facter[:facterversion] + Puppet.features.stubs(:cfacter?).returns true + lambda { Puppet.settings[:cfacter] = true }.should raise_exception ArgumentError, 'facter has already evaluated facts.' + end + + it 'should initialize cfacter when set to true' do + Puppet.features.stubs(:cfacter?).returns true + CFacter = mock + CFacter.stubs(:initialize) + Puppet.settings[:cfacter] = true + end + + end end diff --git a/spec/unit/face/certificate_request_spec.rb b/spec/unit/face/certificate_request_spec.rb deleted file mode 100755 index 60917254f..000000000 --- a/spec/unit/face/certificate_request_spec.rb +++ /dev/null @@ -1,7 +0,0 @@ -#! /usr/bin/env ruby -require 'spec_helper' -require 'puppet/face' - -describe Puppet::Face[:certificate_request, '0.0.1'] do - it "should actually have some tests..." -end diff --git a/spec/unit/face/certificate_revocation_list_spec.rb b/spec/unit/face/certificate_revocation_list_spec.rb deleted file mode 100755 index b833fb718..000000000 --- a/spec/unit/face/certificate_revocation_list_spec.rb +++ /dev/null @@ -1,7 +0,0 @@ -#! /usr/bin/env ruby -require 'spec_helper' -require 'puppet/face' - -describe Puppet::Face[:certificate_revocation_list, '0.0.1'] do - it "should actually have some tests..." -end diff --git a/spec/unit/face/config_spec.rb b/spec/unit/face/config_spec.rb index f42844827..2c43b33b7 100755 --- a/spec/unit/face/config_spec.rb +++ b/spec/unit/face/config_spec.rb @@ -92,6 +92,7 @@ basemodulepath = #{File.expand_path("/some/base")} end it "prints the default configured env settings for an env that does not exist" do + pending "This case no longer exists because Application will through an error before we even get here because of the non-existent environment" Puppet[:environment] = 'doesnotexist' FS.overlay( @@ -125,7 +126,7 @@ basemodulepath = #{File.expand_path("/some/base")} CONF end - it_behaves_like :config_printing_a_section + it_behaves_like :config_printing_a_section, nil end context "from master section" do diff --git a/spec/unit/face/key_spec.rb b/spec/unit/face/key_spec.rb deleted file mode 100755 index 6d9519825..000000000 --- a/spec/unit/face/key_spec.rb +++ /dev/null @@ -1,7 +0,0 @@ -#! /usr/bin/env ruby -require 'spec_helper' -require 'puppet/face' - -describe Puppet::Face[:key, '0.0.1'] do - it "should actually have some tests..." -end diff --git a/spec/unit/face/module/build_spec.rb b/spec/unit/face/module/build_spec.rb index 02bbb7e9d..72ba5e1f1 100644 --- a/spec/unit/face/module/build_spec.rb +++ b/spec/unit/face/module/build_spec.rb @@ -25,7 +25,7 @@ describe "puppet module build" do it "if current directory or parents contain no module root, should return exception" do Dir.expects(:pwd).returns('/a/b/c') Puppet::ModuleTool.expects(:find_module_root).returns(nil) - expect { subject.build }.to raise_error RuntimeError, "Unable to find module root at /a/b/c or parent directories" + expect { subject.build }.to raise_error RuntimeError, "Unable to find metadata.json or Modulefile in module root /a/b/c or parent directories. See <http://links.puppetlabs.com/modulefile> for required file format." end end @@ -39,7 +39,7 @@ describe "puppet module build" do it "if path is not a module root should raise exception" do Puppet::ModuleTool.expects(:is_module_root?).with('/a/b/c').returns(false) - expect { subject.build('/a/b/c') }.to raise_error RuntimeError, "Unable to find module root at /a/b/c" + expect { subject.build('/a/b/c') }.to raise_error RuntimeError, "Unable to find metadata.json or Modulefile in module root /a/b/c. See <http://links.puppetlabs.com/modulefile> for required file format." end end diff --git a/spec/unit/face/module/install_spec.rb b/spec/unit/face/module/install_spec.rb index 528c7822a..3f0b27684 100644 --- a/spec/unit/face/module/install_spec.rb +++ b/spec/unit/face/module/install_spec.rb @@ -7,19 +7,19 @@ describe "puppet module install" do describe "action" do let(:name) { stub(:name) } - let(:target_dir) { stub(:target_dir) } - let(:target_path) { stub(:target_path) } - let(:install_dir) { stub(:install_dir) } + let(:target_dir) { tmpdir('module install face action') } let(:options) { { :target_dir => target_dir } } it 'should invoke the Installer app' do - args = [ name, install_dir, options ] - Puppet::ModuleTool.expects(:set_option_defaults).with(options) + Puppet::ModuleTool::Applications::Installer.expects(:run).with do |*args| + mod, target, opts = args - Pathname.expects(:new).with(target_dir).returns(target_path) - Puppet::ModuleTool::InstallDirectory.expects(:new).with(target_path).returns(install_dir) - Puppet::ModuleTool::Applications::Installer.expects(:run).with(*args) + expect(mod).to eql(name) + expect(opts).to eql(options) + expect(target).to be_a(Puppet::ModuleTool::InstallDirectory) + expect(target.target).to eql(Pathname.new(target_dir)) + end Puppet::Face[:module, :current].install(name, options) end diff --git a/spec/unit/face/parser_spec.rb b/spec/unit/face/parser_spec.rb index 9b1b07a47..e9ba07a2d 100644 --- a/spec/unit/face/parser_spec.rb +++ b/spec/unit/face/parser_spec.rb @@ -8,58 +8,96 @@ describe Puppet::Face[:parser, :current] do let(:parser) { Puppet::Face[:parser, :current] } - context "from an interactive terminal" do - before :each do - from_an_interactive_terminal + context "validate" do + context "from an interactive terminal" do + before :each do + from_an_interactive_terminal + end + + it "validates the configured site manifest when no files are given" do + manifest = file_containing('site.pp', "{ invalid =>") + + configured_environment = Puppet::Node::Environment.create(:default, [], manifest) + Puppet.override(:current_environment => configured_environment) do + expect { parser.validate() }.to exit_with(1) + end + end + + it "validates the given file" do + manifest = file_containing('site.pp', "{ invalid =>") + + expect { parser.validate(manifest) }.to exit_with(1) + end + + it "runs error free when there are no validation errors" do + manifest = file_containing('site.pp', "notify { valid: }") + + parser.validate(manifest) + end + + it "reports missing files" do + 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 + + it "parses supplied manifest files in the context of a directory environment" do + manifest = file_containing('test.pp', "{ invalid =>") + + env = Puppet::Node::Environment.create(:special, []) + env_loader = Puppet::Environments::Static.new(env) + Puppet.override({:environments => env_loader, :current_environment => env}) do + expect { parser.validate(manifest) }.to exit_with(1) + end + + expect(@logs.join).to match(/environment special.*Syntax error at '\{'/) + end + end - it "validates the configured site manifest when no files are given" do - manifest = file_containing('site.pp', "{ invalid =>") + it "validates the contents of STDIN when no files given and STDIN is not a tty" do + from_a_piped_input_of("{ invalid =>") - configured_environment = Puppet::Node::Environment.create(:default, [], manifest) - Puppet.override(:current_environment => configured_environment) do + Puppet.override(:current_environment => Puppet::Node::Environment.create(:special, [])) do expect { parser.validate() }.to exit_with(1) end end + end - it "validates the given file" do - manifest = file_containing('site.pp', "{ invalid =>") - - expect { parser.validate(manifest) }.to exit_with(1) + context "dump" do + it "prints the AST of the passed expression" do + expect(parser.dump({ :e => 'notice hi' })).to eq("(invoke notice hi)\n") end - it "runs error free when there are no validation errors" do - manifest = file_containing('site.pp', "notify { valid: }") + it "prints the AST of the code read from the passed files" do + first_manifest = file_containing('site.pp', "notice hi") + second_manifest = file_containing('site2.pp', "notice bye") - parser.validate(manifest) + output = parser.dump(first_manifest, second_manifest) + + expect(output).to match(/site\.pp.*\(invoke notice hi\)/) + expect(output).to match(/site2\.pp.*\(invoke notice bye\)/) end - it "reports missing files" do - expect do - parser.validate("missing.pp") - end.to raise_error(Puppet::Error, /One or more file\(s\) specified did not exist.*missing\.pp/m) + it "informs the user of files that don't exist" do + expect(parser.dump('does_not_exist_here.pp')).to match(/did not exist:\s*does_not_exist_here\.pp/m) end - it "parses supplied manifest files in the context of a directory environment" do - manifest = file_containing('test.pp', "{ invalid =>") + it "prints the AST of STDIN when no files given and STDIN is not a tty" do + from_a_piped_input_of("notice hi") - env_loader = Puppet::Environments::Static.new( - Puppet::Node::Environment.create(:special, []) - ) - Puppet.override(:environments => env_loader) do - Puppet[:environment] = 'special' - expect { parser.validate(manifest) }.to exit_with(1) + Puppet.override(:current_environment => Puppet::Node::Environment.create(:special, [])) do + expect(parser.dump()).to eq("(invoke notice hi)\n") end - - expect(@logs.join).to match(/environment special.*Syntax error at '\{'/) end - end - - it "validates the contents of STDIN when no files given and STDIN is not a tty" do - from_a_piped_input_of("{ invalid =>") + it "logs an error if the input cannot be parsed" do + output = parser.dump({ :e => '{ invalid =>' }) - expect { parser.validate() }.to exit_with(1) + expect(output).to eq("") + expect(@logs[0].message).to eq("Syntax error at end of file") + expect(@logs[0].level).to eq(:err) + end end def from_an_interactive_terminal diff --git a/spec/unit/face/report_spec.rb b/spec/unit/face/report_spec.rb deleted file mode 100755 index 6fcb49a64..000000000 --- a/spec/unit/face/report_spec.rb +++ /dev/null @@ -1,7 +0,0 @@ -#! /usr/bin/env ruby -require 'spec_helper' -require 'puppet/face' - -describe Puppet::Face[:report, '0.0.1'] do - it "should actually have some tests..." -end diff --git a/spec/unit/face/resource_spec.rb b/spec/unit/face/resource_spec.rb deleted file mode 100755 index 031d78116..000000000 --- a/spec/unit/face/resource_spec.rb +++ /dev/null @@ -1,7 +0,0 @@ -#! /usr/bin/env ruby -require 'spec_helper' -require 'puppet/face' - -describe Puppet::Face[:resource, '0.0.1'] do - it "should actually have some tests..." -end diff --git a/spec/unit/face/resource_type_spec.rb b/spec/unit/face/resource_type_spec.rb deleted file mode 100755 index 5713a01fb..000000000 --- a/spec/unit/face/resource_type_spec.rb +++ /dev/null @@ -1,7 +0,0 @@ -#! /usr/bin/env ruby -require 'spec_helper' -require 'puppet/face' - -describe Puppet::Face[:resource_type, '0.0.1'] do - it "should actually have some tests..." -end diff --git a/spec/unit/file_bucket/file_spec.rb b/spec/unit/file_bucket/file_spec.rb index 370ba1eeb..500e81685 100755 --- a/spec/unit/file_bucket/file_spec.rb +++ b/spec/unit/file_bucket/file_spec.rb @@ -14,7 +14,7 @@ describe Puppet::FileBucket::File, :uses_checksums => true do end it "accepts s and pson" do - expect(Puppet::FileBucket::File.supported_formats).to include(:s, :pson) + expect(Puppet::FileBucket::File.supported_formats).to include(:s, :pson) end describe "making round trips through network formats" do @@ -34,7 +34,7 @@ describe Puppet::FileBucket::File, :uses_checksums => true do end it "should require contents to be a string" do - expect { Puppet::FileBucket::File.new(5) }.to raise_error(ArgumentError, /contents must be a String, got a Fixnum$/) + expect { Puppet::FileBucket::File.new(5) }.to raise_error(ArgumentError, /contents must be a String or Pathname, got a Fixnum$/) end it "should complain about options other than :bucket_path" do diff --git a/spec/unit/file_system/tempfile_spec.rb b/spec/unit/file_system/tempfile_spec.rb deleted file mode 100644 index eb13b0406..000000000 --- a/spec/unit/file_system/tempfile_spec.rb +++ /dev/null @@ -1,48 +0,0 @@ -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.read(file.path)).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.exist?(filename)).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.exist?(filename)).to be_false - end -end diff --git a/spec/unit/file_system/uniquefile_spec.rb b/spec/unit/file_system/uniquefile_spec.rb new file mode 100644 index 000000000..1268581fa --- /dev/null +++ b/spec/unit/file_system/uniquefile_spec.rb @@ -0,0 +1,184 @@ +require 'spec_helper' + +describe Puppet::FileSystem::Uniquefile do + it "makes the name of the file available" do + Puppet::FileSystem::Uniquefile.open_tmp('foo') do |file| + expect(file.path).to match(/foo/) + end + end + + it "provides a writeable file" do + Puppet::FileSystem::Uniquefile.open_tmp('foo') do |file| + file.write("stuff") + file.flush + + expect(Puppet::FileSystem.read(file.path)).to eq("stuff") + end + end + + it "returns the value of the block" do + the_value = Puppet::FileSystem::Uniquefile.open_tmp('foo') do |file| + "my value" + end + + expect(the_value).to eq("my value") + end + + it "unlinks the temporary file" do + filename = Puppet::FileSystem::Uniquefile.open_tmp('foo') do |file| + file.path + end + + expect(Puppet::FileSystem.exist?(filename)).to be_false + end + + it "unlinks the temporary file even if the block raises an error" do + filename = nil + + begin + Puppet::FileSystem::Uniquefile.open_tmp('foo') do |file| + filename = file.path + raise "error!" + end + rescue + end + + expect(Puppet::FileSystem.exist?(filename)).to be_false + end + + + context "Ruby 1.9.3 Tempfile tests" do + # the remaining tests in this file are ported directly from the ruby 1.9.3 source, + # since most of this file was ported from there + # see: https://github.com/ruby/ruby/blob/v1_9_3_547/test/test_tempfile.rb + + def tempfile(*args, &block) + t = Puppet::FileSystem::Uniquefile.new(*args, &block) + @tempfile = (t unless block) + end + + after(:each) do + if @tempfile + @tempfile.close! + end + end + + it "creates tempfiles" do + t = tempfile("foo") + path = t.path + t.write("hello world") + t.close + expect(File.read(path)).to eq("hello world") + end + + it "saves in tmpdir by default" do + t = tempfile("foo") + expect(Dir.tmpdir).to eq(File.dirname(t.path)) + end + + it "saves in given directory" do + subdir = File.join(Dir.tmpdir, "tempfile-test-#{rand}") + Dir.mkdir(subdir) + begin + tempfile = Tempfile.new("foo", subdir) + tempfile.close + begin + expect(subdir).to eq(File.dirname(tempfile.path)) + ensure + tempfile.unlink + end + ensure + Dir.rmdir(subdir) + end + end + + it "supports basename" do + t = tempfile("foo") + expect(File.basename(t.path)).to match(/^foo/) + end + + it "supports basename with suffix" do + t = tempfile(["foo", ".txt"]) + expect(File.basename(t.path)).to match(/^foo/) + expect(File.basename(t.path)).to match(/\.txt$/) + end + + it "supports unlink" do + t = tempfile("foo") + path = t.path + t.close + expect(File.exist?(path)).to eq(true) + t.unlink + expect(File.exist?(path)).to eq(false) + expect(t.path).to eq(nil) + end + + it "supports closing" do + t = tempfile("foo") + expect(t.closed?).to eq(false) + t.close + expect(t.closed?).to eq(true) + end + + it "supports closing and unlinking via boolean argument" do + t = tempfile("foo") + path = t.path + t.close(true) + expect(t.closed?).to eq(true) + expect(t.path).to eq(nil) + expect(File.exist?(path)).to eq(false) + end + + context "on unix platforms", :unless => Puppet.features.microsoft_windows? do + it "close doesn't unlink if already unlinked" do + t = tempfile("foo") + path = t.path + t.unlink + File.open(path, "w").close + begin + t.close(true) + expect(File.exist?(path)).to eq(true) + ensure + File.unlink(path) rescue nil + end + end + end + + it "supports close!" do + t = tempfile("foo") + path = t.path + t.close! + expect(t.closed?).to eq(true) + expect(t.path).to eq(nil) + expect(File.exist?(path)).to eq(false) + end + + context "on unix platforms", :unless => Puppet.features.microsoft_windows? do + it "close! doesn't unlink if already unlinked" do + t = tempfile("foo") + path = t.path + t.unlink + File.open(path, "w").close + begin + t.close! + expect(File.exist?(path)).to eq(true) + ensure + File.unlink(path) rescue nil + end + end + end + + it "close does not make path nil" do + t = tempfile("foo") + t.close + expect(t.path.nil?).to eq(false) + end + + it "close flushes buffer" do + t = tempfile("foo") + t.write("hello") + t.close + expect(File.size(t.path)).to eq(5) + end + end +end diff --git a/spec/unit/forge/errors_spec.rb b/spec/unit/forge/errors_spec.rb index 057bf014a..fc1ac1a0e 100644 --- a/spec/unit/forge/errors_spec.rb +++ b/spec/unit/forge/errors_spec.rb @@ -47,15 +47,14 @@ Could not connect to http://fake.com:1111 let(:exception) { subject.new(:uri => 'http://fake.com:1111', :response => response, :input => 'user/module') } it 'should return a valid single line error' do - exception.message.should == 'Could not execute operation for \'user/module\'. Detail: 404 not found.' + exception.message.should == 'Request to Puppet Forge failed. Detail: 404 not found.' end it 'should return a valid multiline error' do exception.multiline.should == <<-eos.chomp -Could not execute operation for 'user/module' +Request to Puppet Forge failed. The server being queried was http://fake.com:1111 The HTTP response we received was '404 not found' - Check the author and module names are correct. eos end end @@ -64,16 +63,15 @@ Could not execute operation for 'user/module' let(:exception) { subject.new(:uri => 'http://fake.com:1111', :response => response, :input => 'user/module', :message => 'no such module') } it 'should return a valid single line error' do - exception.message.should == 'Could not execute operation for \'user/module\'. Detail: no such module / 404 not found.' + exception.message.should == 'Request to Puppet Forge failed. Detail: no such module / 404 not found.' end it 'should return a valid multiline error' do exception.multiline.should == <<-eos.chomp -Could not execute operation for 'user/module' +Request to Puppet Forge failed. The server being queried was http://fake.com:1111 The HTTP response we received was '404 not found' The message we received said 'no such module' - Check the author and module names are correct. eos end end diff --git a/spec/unit/forge/module_release_spec.rb b/spec/unit/forge/module_release_spec.rb index fbf5e157a..b763f0833 100644 --- a/spec/unit/forge/module_release_spec.rb +++ b/spec/unit/forge/module_release_spec.rb @@ -8,57 +8,14 @@ describe Puppet::Forge::ModuleRelease do let(:agent) { "Test/1.0" } let(:repository) { Puppet::Forge::Repository.new('http://fake.com', agent) } let(:ssl_repository) { Puppet::Forge::Repository.new('https://fake.com', agent) } - - let(:release_json) do - <<-EOF - { - "uri": "/v3/releases/puppetlabs-stdlib-4.1.0", - "module": { - "uri": "/v3/modules/puppetlabs-stdlib", - "name": "stdlib", - "owner": { - "uri": "/v3/users/puppetlabs", - "username": "puppetlabs", - "gravatar_id": "fdd009b7c1ec96e088b389f773e87aec" - } - }, - "version": "4.1.0", - "metadata": { - "types": [ ], - "license": "Apache 2.0", - "checksums": { }, - "version": "4.1.0", - "description": "Standard Library for Puppet Modules", - "source": "git://github.com/puppetlabs/puppetlabs-stdlib.git", - "project_page": "https://github.com/puppetlabs/puppetlabs-stdlib", - "summary": "Puppet Module Standard Library", - "dependencies": [ - - ], - "author": "puppetlabs", - "name": "puppetlabs-stdlib" - }, - "tags": [ - "puppetlabs", - "library", - "stdlib", - "standard", - "stages" - ], - "file_uri": "/v3/files/puppetlabs-stdlib-4.1.0.tar.gz", - "file_size": 67586, - "file_md5": "bbf919d7ee9d278d2facf39c25578bf8", - "downloads": 610751, - "readme": "", - "changelog": "", - "license": "", - "created_at": "2013-05-13 08:31:19 -0700", - "updated_at": "2013-05-13 08:31:19 -0700", - "deleted_at": null - } - EOF - end - + let(:api_version) { "v3" } + let(:module_author) { "puppetlabs" } + let(:module_name) { "stdlib" } + let(:module_version) { "4.1.0" } + let(:module_full_name) { "#{module_author}-#{module_name}" } + let(:module_full_name_versioned) { "#{module_full_name}-#{module_version}" } + let(:module_md5) { "bbf919d7ee9d278d2facf39c25578bf8" } + let(:uri) { " "} let(:release) { Puppet::Forge::ModuleRelease.new(ssl_repository, JSON.parse(release_json)) } let(:mock_file) { @@ -69,63 +26,195 @@ describe Puppet::Forge::ModuleRelease do let(:mock_dir) { '/tmp' } - def mock_digest_file_with_md5(md5) - Digest::MD5.stubs(:file).returns(stub(:hexdigest => md5)) - end - - describe '#prepare' do - before :each do - release.stubs(:tmpfile).returns(mock_file) - release.stubs(:tmpdir).returns(mock_dir) + shared_examples 'a module release' do + def mock_digest_file_with_md5(md5) + Digest::MD5.stubs(:file).returns(stub(:hexdigest => md5)) end - it 'should call sub methods with correct params' do - release.expects(:download).with('/v3/files/puppetlabs-stdlib-4.1.0.tar.gz', mock_file) - release.expects(:validate_checksum).with(mock_file, 'bbf919d7ee9d278d2facf39c25578bf8') - release.expects(:unpack).with(mock_file, mock_dir) + describe '#prepare' do + before :each do + release.stubs(:tmpfile).returns(mock_file) + release.stubs(:tmpdir).returns(mock_dir) + end + + it 'should call sub methods with correct params' do + release.expects(:download).with("/#{api_version}/files/#{module_full_name_versioned}.tar.gz", mock_file) + release.expects(:validate_checksum).with(mock_file, module_md5) + release.expects(:unpack).with(mock_file, mock_dir) - release.prepare + release.prepare + end end - end - describe '#tmpfile' do + describe '#tmpfile' do - # This is impossible to test under Ruby 1.8.x, but should also occur there. - it 'should be opened in binary mode', :unless => RUBY_VERSION >= '1.8.7' do - Puppet::Forge::Cache.stubs(:base_path).returns(Dir.tmpdir) - release.send(:tmpfile).binmode?.should be_true + # This is impossible to test under Ruby 1.8.x, but should also occur there. + it 'should be opened in binary mode', :unless => RUBY_VERSION >= '1.8.7' do + Puppet::Forge::Cache.stubs(:base_path).returns(Dir.tmpdir) + release.send(:tmpfile).binmode?.should be_true + end end - end - describe '#download' do - it 'should call make_http_request with correct params' do - # valid URI comes from file_uri in JSON blob above - ssl_repository.expects(:make_http_request).with('/v3/files/puppetlabs-stdlib-4.1.0.tar.gz', mock_file).returns(mock_file) + describe '#download' do + it 'should call make_http_request with correct params' do + # valid URI comes from file_uri in JSON blob above + ssl_repository.expects(:make_http_request).with("/#{api_version}/files/#{module_full_name_versioned}.tar.gz", mock_file).returns(stub(:body => '{}', :code => '200')) + + release.send(:download, "/#{api_version}/files/#{module_full_name_versioned}.tar.gz", mock_file) + end - release.send(:download, '/v3/files/puppetlabs-stdlib-4.1.0.tar.gz', mock_file) + it 'should raise a response error when it receives an error from forge' do + ssl_repository.stubs(:make_http_request).returns(stub(:body => '{"errors": ["error"]}', :code => '500', :message => 'server error')) + expect { release.send(:download, "/some/path", mock_file)}. to raise_error Puppet::Forge::Errors::ResponseError + end end - end - describe '#verify_checksum' do - it 'passes md5 check when valid' do - # valid hash comes from file_md5 in JSON blob above - mock_digest_file_with_md5('bbf919d7ee9d278d2facf39c25578bf8') + describe '#verify_checksum' do + it 'passes md5 check when valid' do + # valid hash comes from file_md5 in JSON blob above + mock_digest_file_with_md5(module_md5) - release.send(:validate_checksum, mock_file, 'bbf919d7ee9d278d2facf39c25578bf8') + release.send(:validate_checksum, mock_file, module_md5) + end + + it 'fails md5 check when invalid' do + mock_digest_file_with_md5('ffffffffffffffffffffffffffffffff') + + expect { release.send(:validate_checksum, mock_file, module_md5) }.to raise_error(RuntimeError, /did not match expected checksum/) + end end - it 'fails md5 check when invalid' do - mock_digest_file_with_md5('ffffffffffffffffffffffffffffffff') + describe '#unpack' do + it 'should call unpacker with correct params' do + Puppet::ModuleTool::Applications::Unpacker.expects(:unpack).with(mock_file.path, mock_dir).returns(true) + + release.send(:unpack, mock_file, mock_dir) + end + end + end - expect { release.send(:validate_checksum, mock_file, 'bbf919d7ee9d278d2facf39c25578bf8') }.to raise_error(RuntimeError, /did not match expected checksum/) + context 'standard forge module' do + let(:release_json) do %Q{ + { + "uri": "/#{api_version}/releases/#{module_full_name_versioned}", + "module": { + "uri": "/#{api_version}/modules/#{module_full_name}", + "name": "#{module_name}", + "owner": { + "uri": "/#{api_version}/users/#{module_author}", + "username": "#{module_author}", + "gravatar_id": "fdd009b7c1ec96e088b389f773e87aec" + } + }, + "version": "#{module_version}", + "metadata": { + "types": [ ], + "license": "Apache 2.0", + "checksums": { }, + "version": "#{module_version}", + "description": "Standard Library for Puppet Modules", + "source": "git://github.com/puppetlabs/puppetlabs-stdlib.git", + "project_page": "https://github.com/puppetlabs/puppetlabs-stdlib", + "summary": "Puppet Module Standard Library", + "dependencies": [ + + ], + "author": "#{module_author}", + "name": "#{module_full_name}" + }, + "tags": [ + "puppetlabs", + "library", + "stdlib", + "standard", + "stages" + ], + "file_uri": "/#{api_version}/files/#{module_full_name_versioned}.tar.gz", + "file_size": 67586, + "file_md5": "#{module_md5}", + "downloads": 610751, + "readme": "", + "changelog": "", + "license": "", + "created_at": "2013-05-13 08:31:19 -0700", + "updated_at": "2013-05-13 08:31:19 -0700", + "deleted_at": null + } + } end + + it_behaves_like 'a module release' end - describe '#unpack' do - it 'should call unpacker with correct params' do - Puppet::ModuleTool::Applications::Unpacker.expects(:unpack).with(mock_file.path, mock_dir).returns(true) + context 'forge module with no dependencies field' do + let(:release_json) do %Q{ + { + "uri": "/#{api_version}/releases/#{module_full_name_versioned}", + "module": { + "uri": "/#{api_version}/modules/#{module_full_name}", + "name": "#{module_name}", + "owner": { + "uri": "/#{api_version}/users/#{module_author}", + "username": "#{module_author}", + "gravatar_id": "fdd009b7c1ec96e088b389f773e87aec" + } + }, + "version": "#{module_version}", + "metadata": { + "types": [ ], + "license": "Apache 2.0", + "checksums": { }, + "version": "#{module_version}", + "description": "Standard Library for Puppet Modules", + "source": "git://github.com/puppetlabs/puppetlabs-stdlib.git", + "project_page": "https://github.com/puppetlabs/puppetlabs-stdlib", + "summary": "Puppet Module Standard Library", + "author": "#{module_author}", + "name": "#{module_full_name}" + }, + "tags": [ + "puppetlabs", + "library", + "stdlib", + "standard", + "stages" + ], + "file_uri": "/#{api_version}/files/#{module_full_name_versioned}.tar.gz", + "file_size": 67586, + "file_md5": "#{module_md5}", + "downloads": 610751, + "readme": "", + "changelog": "", + "license": "", + "created_at": "2013-05-13 08:31:19 -0700", + "updated_at": "2013-05-13 08:31:19 -0700", + "deleted_at": null + } + } + end + + it_behaves_like 'a module release' + end - release.send(:unpack, mock_file, mock_dir) + context 'forge module with the minimal set of fields' do + let(:release_json) do %Q{ + { + "uri": "/#{api_version}/releases/#{module_full_name_versioned}", + "module": { + "uri": "/#{api_version}/modules/#{module_full_name}", + "name": "#{module_name}" + }, + "metadata": { + "version": "#{module_version}", + "name": "#{module_full_name}" + }, + "file_uri": "/#{api_version}/files/#{module_full_name_versioned}.tar.gz", + "file_size": 67586, + "file_md5": "#{module_md5}" + } + } end + + it_behaves_like 'a module release' end end diff --git a/spec/unit/forge/repository_spec.rb b/spec/unit/forge/repository_spec.rb index 04b10b166..4ac77ecb8 100644 --- a/spec/unit/forge/repository_spec.rb +++ b/spec/unit/forge/repository_spec.rb @@ -80,6 +80,20 @@ describe Puppet::Forge::Repository do request['User-Agent'].should =~ /\bRuby\b/ end + it "Does not set Authorization header by default" do + Puppet.features.stubs(:pe_license?).returns(false) + Puppet[:forge_authorization] = nil + request = repository.get_request_object("the_path") + request['Authorization'].should == nil + end + + it "Sets Authorization header from config" do + token = 'bearer some token' + Puppet[:forge_authorization] = token + request = repository.get_request_object("the_path") + request['Authorization'].should == token + end + it "escapes the received URI" do unescaped_uri = "héllo world !! ç à " performs_an_http_request do |http| @@ -97,7 +111,7 @@ describe Puppet::Forge::Repository do proxy = mock("http proxy") proxy_class.expects(:new).with("fake.com", 80).returns(proxy) proxy.expects(:start).yields(http).returns(result) - Net::HTTP.expects(:Proxy).with("proxy", 1234).returns(proxy_class) + Net::HTTP.expects(:Proxy).with("proxy", 1234, nil, nil).returns(proxy_class) end def performs_an_https_request(result = nil, &block) @@ -111,7 +125,94 @@ describe Puppet::Forge::Repository do proxy.expects(:use_ssl=).with(true) proxy.expects(:cert_store=) proxy.expects(:verify_mode=).with(OpenSSL::SSL::VERIFY_PEER) - Net::HTTP.expects(:Proxy).with("proxy", 1234).returns(proxy_class) + Net::HTTP.expects(:Proxy).with("proxy", 1234, nil, nil).returns(proxy_class) + end + end + + describe "making a request against an authentiated proxy" do + before :each do + authenticated_proxy_settings_of("proxy", 1234, 'user1', 'password') + end + + it "returns the result object from the request" do + result = "#{Object.new}" + + performs_an_authenticated_http_request result do |http| + http.expects(:request).with(responds_with(:path, "the_path")) + end + + repository.make_http_request("the_path").should == result + end + + it 'returns the result object from a request with ssl' do + result = "#{Object.new}" + performs_an_authenticated_https_request result do |http| + http.expects(:request).with(responds_with(:path, "the_path")) + end + + ssl_repository.make_http_request("the_path").should == result + end + + it 'return a valid exception when there is an SSL verification problem' do + performs_an_authenticated_https_request "#{Object.new}" do |http| + http.expects(:request).with(responds_with(:path, "the_path")).raises OpenSSL::SSL::SSLError.new("certificate verify failed") + end + + expect { ssl_repository.make_http_request("the_path") }.to raise_error Puppet::Forge::Errors::SSLVerifyError, 'Unable to verify the SSL certificate at https://fake.com' + end + + it 'return a valid exception when there is a communication problem' do + performs_an_authenticated_http_request "#{Object.new}" do |http| + http.expects(:request).with(responds_with(:path, "the_path")).raises SocketError + end + + expect { repository.make_http_request("the_path") }. + to raise_error Puppet::Forge::Errors::CommunicationError, + 'Unable to connect to the server at http://fake.com. Detail: SocketError.' + end + + it "sets the user agent for the request" do + path = 'the_path' + + request = repository.get_request_object(path) + + request['User-Agent'].should =~ /\b#{agent}\b/ + request['User-Agent'].should =~ /\bPuppet\b/ + request['User-Agent'].should =~ /\bRuby\b/ + end + + it "escapes the received URI" do + unescaped_uri = "héllo world !! ç à " + performs_an_authenticated_http_request do |http| + http.expects(:request).with(responds_with(:path, URI.escape(unescaped_uri))) + end + + repository.make_http_request(unescaped_uri) + end + + def performs_an_authenticated_http_request(result = nil, &block) + http = mock("http client") + yield http + + proxy_class = mock("http proxy class") + proxy = mock("http proxy") + proxy_class.expects(:new).with("fake.com", 80).returns(proxy) + proxy.expects(:start).yields(http).returns(result) + Net::HTTP.expects(:Proxy).with("proxy", 1234, "user1", "password").returns(proxy_class) + end + + def performs_an_authenticated_https_request(result = nil, &block) + http = mock("http client") + yield http + + proxy_class = mock("http proxy class") + proxy = mock("http proxy") + proxy_class.expects(:new).with("fake.com", 443).returns(proxy) + proxy.expects(:start).yields(http).returns(result) + proxy.expects(:use_ssl=).with(true) + proxy.expects(:cert_store=) + proxy.expects(:verify_mode=).with(OpenSSL::SSL::VERIFY_PEER) + Net::HTTP.expects(:Proxy).with("proxy", 1234, "user1", "password").returns(proxy_class) end end @@ -119,4 +220,11 @@ describe Puppet::Forge::Repository do Puppet[:http_proxy_host] = host Puppet[:http_proxy_port] = port end + + def authenticated_proxy_settings_of(host, port, user, password) + Puppet[:http_proxy_host] = host + Puppet[:http_proxy_port] = port + Puppet[:http_proxy_user] = user + Puppet[:http_proxy_password] = password + end end diff --git a/spec/unit/forge_spec.rb b/spec/unit/forge_spec.rb index 96bf8d3be..eb7c56a3e 100644 --- a/spec/unit/forge_spec.rb +++ b/spec/unit/forge_spec.rb @@ -110,17 +110,38 @@ describe Puppet::Forge do forge.search('bacula').should == search_results end + context "when module_groups are defined" do + let(:release_response) do + releases = JSON.parse(http_response) + releases['results'] = [] + JSON.dump(releases) + end + + before :each do + repository_responds_with(stub(:body => release_response, :code => '200')).with {|uri| uri =~ /module_groups=foo/} + Puppet[:module_groups] = "foo" + end + + it "passes module_groups with search" do + forge.search('bacula') + end + + it "passes module_groups with fetch" do + forge.fetch('puppetlabs-bacula') + end + end + context "when the connection to the forge fails" do before :each do repository_responds_with(stub(:body => '{}', :code => '404', :message => "not found")) end it "raises an error for search" do - expect { forge.search('bacula') }.to raise_error Puppet::Forge::Errors::ResponseError, "Could not execute operation for 'bacula'. Detail: 404 not found." + expect { forge.search('bacula') }.to raise_error Puppet::Forge::Errors::ResponseError, "Request to Puppet Forge failed. Detail: 404 not found." end it "raises an error for fetch" do - expect { forge.fetch('puppetlabs/bacula') }.to raise_error Puppet::Forge::Errors::ResponseError, "Could not execute operation for 'puppetlabs/bacula'. Detail: 404 not found." + expect { forge.fetch('puppetlabs/bacula') }.to raise_error Puppet::Forge::Errors::ResponseError, "Request to Puppet Forge failed. Detail: 404 not found." end end @@ -130,7 +151,22 @@ describe Puppet::Forge do end it "raises an error for fetch" do - expect { forge.fetch('puppetlabs/bacula') }.to raise_error Puppet::Forge::Errors::ResponseError, "Could not execute operation for 'puppetlabs/bacula'. Detail: 410 Gone." + expect { forge.fetch('puppetlabs/bacula') }.to raise_error Puppet::Forge::Errors::ResponseError, "Request to Puppet Forge failed. Detail: 410 Gone." + end + end + + context "when the forge returns a module with unparseable dependencies" do + before :each do + response = JSON.parse(http_response) + release = response['results'][0]['current_release'] + release['metadata']['dependencies'] = [{'name' => 'broken-garbage >= 1.0.0', 'version_requirement' => 'banana'}] + response['results'] = [release] + repository_responds_with(stub(:body => JSON.dump(response), :code => '200')) + end + + it "ignores modules with unparseable dependencies" do + expect { result = forge.fetch('puppetlabs/bacula') }.to_not raise_error + expect { result.to be_empty } end end end diff --git a/spec/unit/functions/assert_type_spec.rb b/spec/unit/functions/assert_type_spec.rb index d47f47e30..13b353401 100644 --- a/spec/unit/functions/assert_type_spec.rb +++ b/spec/unit/functions/assert_type_spec.rb @@ -37,8 +37,8 @@ describe 'the assert_type function' do end.to raise_error(ArgumentError, Regexp.new(Regexp.escape( "function 'assert_type' called with mis-matched arguments expected one of: - assert_type(Type type, Optional[Object] value) - arg count {2} - assert_type(String type_string, Optional[Object] value) - arg count {2} + assert_type(Type type, Any value, Callable[Type, Type] block {0,1}) - arg count {2,3} + assert_type(String type_string, Any value, Callable[Type, Type] block {0,1}) - arg count {2,3} actual: assert_type(Integer, Integer) - arg count {2}"))) end @@ -46,7 +46,13 @@ actual: it 'allows the second arg to be undef/nil)' do expect do func.call({}, optional(String), nil) - end.to_not raise_error(ArgumentError) + end.to_not raise_error + end + + it 'can be called with a callable that receives a specific type' do + expected, actual = func.call({}, optional(String), 1, create_callable_2_args_unit) + expect(expected.to_s).to eql('Optional[String]') + expect(actual.to_s).to eql('Integer[1, 1]') end def optional(type_ref) @@ -56,4 +62,17 @@ actual: def type(type_ref) Puppet::Pops::Types::TypeFactory.type_of(type_ref) end + + def create_callable_2_args_unit() + Puppet::Functions.create_function(:func) do + dispatch :func do + param 'Type', 'expected' + param 'Type', 'actual' + end + + def func(expected, actual) + [expected, actual] + end + end.new({}, nil) + end end diff --git a/spec/unit/parser/methods/each_spec.rb b/spec/unit/functions/each_spec.rb index 5e9ce4e0c..d6651cf5a 100644 --- a/spec/unit/parser/methods/each_spec.rb +++ b/spec/unit/functions/each_spec.rb @@ -1,7 +1,8 @@ require 'puppet' require 'spec_helper' require 'puppet_spec/compiler' -require 'rubygems' + +require 'shared_behaviours/iterative_functions' describe 'the each method' do include PuppetSpec::Compiler @@ -23,6 +24,7 @@ describe 'the each method' do catalog.resource(:file, "/file_2")['ensure'].should == 'present' catalog.resource(:file, "/file_3")['ensure'].should == 'present' end + it 'each on an array selecting each value - function call style' do catalog = compile_to_catalog(<<-MANIFEST) $a = [1,2,3] @@ -61,6 +63,7 @@ describe 'the each method' do catalog.resource(:file, "/file_b")['ensure'].should == 'absent' catalog.resource(:file, "/file_c")['ensure'].should == 'present' end + it 'each on a hash selecting key and value' do catalog = compile_to_catalog(<<-MANIFEST) $a = {'a'=>present,'b'=>absent,'c'=>present} @@ -73,7 +76,21 @@ describe 'the each method' do catalog.resource(:file, "/file_b")['ensure'].should == 'absent' catalog.resource(:file, "/file_c")['ensure'].should == 'present' end + + it 'each on a hash selecting key and value (using captures-last parameter)' do + catalog = compile_to_catalog(<<-MANIFEST) + $a = {'a'=>present,'b'=>absent,'c'=>present} + $a.each |*$kv| { + file { "/file_${kv[0]}": ensure => $kv[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 end + context "should produce receiver" do it 'each checking produced value using single expression' do catalog = compile_to_catalog(<<-MANIFEST) @@ -88,4 +105,7 @@ describe 'the each method' do end end + it_should_behave_like 'all iterative functions argument checks', 'each' + it_should_behave_like 'all iterative functions hash handling', 'each' + end diff --git a/spec/unit/parser/functions/epp_spec.rb b/spec/unit/functions/epp_spec.rb index b88d3da8f..382fd9548 100644 --- a/spec/unit/parser/functions/epp_spec.rb +++ b/spec/unit/functions/epp_spec.rb @@ -27,7 +27,7 @@ describe "the epp function" do end it "get nil accessing a variable that is undef" do - scope['undef_var'] = :undef + scope['undef_var'] = nil eval_template("<%= $undef_var == undef %>").should == "true" end @@ -36,26 +36,74 @@ describe "the epp function" do eval_template_with_args("<%= $phantom == dragos %>", 'phantom' => 'dragos').should == "true" end + it "can use values from the enclosing scope for defaults" do + scope['phantom'] = 'of the opera' + eval_template("<%- |$phantom = $phantom| -%><%= $phantom %>").should == "of the opera" + end + + it "uses the default value if the given value is undef/nil" do + eval_template_with_args("<%- |$phantom = 'inside your mind'| -%><%= $phantom %>", 'phantom' => nil).should == "inside your mind" + end + it "gets shadowed variable if args are given and parameters are specified" do scope['x'] = 'wrong one' - eval_template_with_args("<%-( $x )-%><%= $x == correct %>", 'x' => 'correct').should == "true" + eval_template_with_args("<%- |$x| -%><%= $x == correct %>", 'x' => 'correct').should == "true" end it "raises an error if required variable is not given" do scope['x'] = 'wrong one' - expect { + expect do eval_template_with_args("<%-| $x |-%><%= $x == correct %>", 'y' => 'correct') - }.to raise_error(/no value given for required parameters x/) + end.to raise_error(/no value given for required parameters x/) end it "raises an error if too many arguments are given" do scope['x'] = 'wrong one' - expect { + expect do eval_template_with_args("<%-| $x |-%><%= $x == correct %>", 'x' => 'correct', 'y' => 'surplus') - }.to raise_error(/Too many arguments: 2 for 1/) + end.to raise_error(/Too many arguments: 2 for 1/) + end + end + + context "when given an empty template" do + it "allows the template file to be empty" do + expect(eval_template("")).to eq("") + end + + it "allows the template to have empty body after parameters" do + expect(eval_template_with_args("<%-|$x|%>", 'x'=>1)).to eq("") end end + context "when using typed parameters" do + it "allows a passed value that matches the parameter's type" do + expect(eval_template_with_args("<%-|String $x|-%><%= $x == correct %>", 'x' => 'correct')).to eq("true") + end + + it "does not allow slurped parameters" do + expect do + eval_template_with_args("<%-|*$x|-%><%= $x %>", 'x' => 'incorrect') + end.to raise_error(/'captures rest' - not supported in an Epp Template/) + end + + it "raises an error when the passed value does not match the parameter's type" do + expect do + eval_template_with_args("<%-|Integer $x|-%><%= $x %>", 'x' => 'incorrect') + end.to raise_error(/expected.*Integer.*actual.*String/m) + end + + it "raises an error when the default value does not match the parameter's type" do + expect do + eval_template("<%-|Integer $x = 'nope'|-%><%= $x %>") + end.to raise_error(/expected.*Integer.*actual.*String/m) + end + + it "allows an parameter to default to undef" do + expect(eval_template("<%-|Optional[Integer] $x = undef|-%><%= $x == undef %>")).to eq("true") + end + end + + # although never a problem with epp it "is not interfered with by having a variable named 'string' (#14093)" do scope['string'] = "this output should not be seen" @@ -78,7 +126,7 @@ describe "the epp function" do }}) Puppet.override({:current_environment => (env = Puppet::Node::Environment.create(:testload, [ modules_dir ]))}, "test") do node.environment = env - expect(scope.function_epp([ 'testmodule/the_x.epp', { 'x' => '3'} ])).to eql("The x is 3") + expect(epp_function.call(scope, 'testmodule/the_x.epp', { 'x' => '3'} )).to eql("The x is 3") end end end @@ -89,7 +137,7 @@ describe "the epp function" do File.open(filename, "w+") { |f| f.write(content) } Puppet::Parser::Files.stubs(:find_template).returns(filename) - scope.function_epp(['template', args_hash]) + epp_function.call(scope, 'template', args_hash) end def eval_template(content) @@ -98,6 +146,10 @@ describe "the epp function" do File.open(filename, "w+") { |f| f.write(content) } Puppet::Parser::Files.stubs(:find_template).returns(filename) - scope.function_epp(['template']) + epp_function.call(scope, 'template') + end + + def epp_function() + epp_func = scope.compiler.loaders.public_environment_loader.load(:function, 'epp') end end diff --git a/spec/unit/parser/methods/filter_spec.rb b/spec/unit/functions/filter_spec.rb index c9fed31d2..e904c6751 100644 --- a/spec/unit/parser/methods/filter_spec.rb +++ b/spec/unit/functions/filter_spec.rb @@ -1,11 +1,13 @@ require 'puppet' require 'spec_helper' require 'puppet_spec/compiler' +require 'matchers/resource' -require 'unit/parser/methods/shared' +require 'shared_behaviours/iterative_functions' describe 'the filter method' do include PuppetSpec::Compiler + include Matchers::Resource before :each do Puppet[:parser] = 'future' @@ -19,8 +21,8 @@ describe 'the filter method' do } MANIFEST - catalog.resource(:file, "/file_strawberry")['ensure'].should == 'present' - catalog.resource(:file, "/file_blueberry")['ensure'].should == 'present' + expect(catalog).to have_resource("File[/file_strawberry]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_blueberry]").with_parameter(:ensure, 'present') end it 'should filter on enumerable type (Integer)' do @@ -31,9 +33,9 @@ describe 'the filter method' do } MANIFEST - catalog.resource(:file, "/file_3")['ensure'].should == 'present' - catalog.resource(:file, "/file_6")['ensure'].should == 'present' - catalog.resource(:file, "/file_9")['ensure'].should == 'present' + expect(catalog).to have_resource("File[/file_3]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_6]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_9]").with_parameter(:ensure, 'present') end it 'should filter on enumerable type (Integer) using two args index/value' do @@ -44,9 +46,9 @@ describe 'the filter method' do } MANIFEST - catalog.resource(:file, "/file_10")['ensure'].should == 'present' - catalog.resource(:file, "/file_13")['ensure'].should == 'present' - catalog.resource(:file, "/file_16")['ensure'].should == 'present' + expect(catalog).to have_resource("File[/file_10]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_13]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_16]").with_parameter(:ensure, 'present') end it 'should produce an array when acting on an array' do @@ -57,8 +59,8 @@ describe 'the filter method' do file { "/file_${b[1]}": ensure => present } MANIFEST - catalog.resource(:file, "/file_strawberry")['ensure'].should == 'present' - catalog.resource(:file, "/file_blueberry")['ensure'].should == 'present' + expect(catalog).to have_resource("File[/file_strawberry]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_blueberry]").with_parameter(:ensure, 'present') end it 'can filter array using index and value' do @@ -69,8 +71,20 @@ describe 'the filter method' do file { "/file_${b[1]}": ensure => present } MANIFEST - catalog.resource(:file, "/file_strawberry")['ensure'].should == 'present' - catalog.resource(:file, "/file_orange")['ensure'].should == 'present' + expect(catalog).to have_resource("File[/file_strawberry]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_orange]").with_parameter(:ensure, 'present') + end + + it 'can filter array using index and value (using captures-rest)' do + catalog = compile_to_catalog(<<-MANIFEST) + $a = ['strawberry','blueberry','orange'] + $b = $a.filter |*$ix|{ $ix[0] == 0 or $ix[0] ==2} + file { "/file_${b[0]}": ensure => present } + file { "/file_${b[1]}": ensure => present } + MANIFEST + + expect(catalog).to have_resource("File[/file_strawberry]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_orange]").with_parameter(:ensure, 'present') end it 'filters on a hash (all berries) by key' do @@ -81,8 +95,8 @@ describe 'the filter method' do } MANIFEST - catalog.resource(:file, "/file_strawberry")['ensure'].should == 'present' - catalog.resource(:file, "/file_blueberry")['ensure'].should == 'present' + expect(catalog).to have_resource("File[/file_strawberry]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_blueberry]").with_parameter(:ensure, 'present') end it 'should produce a hash when acting on a hash' do @@ -95,9 +109,9 @@ describe 'the filter method' do MANIFEST - catalog.resource(:file, "/file_red")['ensure'].should == 'present' - catalog.resource(:file, "/file_blue")['ensure'].should == 'present' - catalog.resource(:file, "/file_")['ensure'].should == 'present' + expect(catalog).to have_resource("File[/file_red]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_blue]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_]").with_parameter(:ensure, 'present') end it 'filters on a hash (all berries) by value' do @@ -108,26 +122,8 @@ describe 'the filter method' do } MANIFEST - catalog.resource(:file, "/file_strawb")['ensure'].should == 'present' - catalog.resource(:file, "/file_blueb")['ensure'].should == 'present' - end - - context 'filter checks arguments and' do - it 'raises an error when block has more than 2 argument' do - expect do - compile_to_catalog(<<-MANIFEST) - [1].filter |$indexm, $x, $yikes|{ } - MANIFEST - end.to raise_error(Puppet::Error, /block must define at most two parameters/) - end - - it 'raises an error when block has fewer than 1 argument' do - expect do - compile_to_catalog(<<-MANIFEST) - [1].filter || { } - MANIFEST - end.to raise_error(Puppet::Error, /block must define at least one parameter/) - end + expect(catalog).to have_resource("File[/file_strawb]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_blueb]").with_parameter(:ensure, 'present') end it_should_behave_like 'all iterative functions argument checks', 'filter' diff --git a/spec/unit/parser/functions/inline_epp_spec.rb b/spec/unit/functions/inline_epp_spec.rb index 44b24528b..36328c2dc 100644 --- a/spec/unit/parser/functions/inline_epp_spec.rb +++ b/spec/unit/functions/inline_epp_spec.rb @@ -56,8 +56,18 @@ describe "the inline_epp function" do end end + context "when given an empty template" do + it "allows the template file to be empty" do + expect(eval_template("")).to eq("") + end + + it "allows the template to have empty body after parameters" do + expect(eval_template_with_args("<%-|$x|%>", 'x'=>1)).to eq("") + end + end + it "renders a block expression" do - eval_template_with_args("<%= {($x) $x + 1} %>", 'x' => 2).should == "3" + eval_template_with_args("<%= { $y = $x $x + 1} %>", 'x' => 2).should == "3" end # although never a problem with epp @@ -73,10 +83,15 @@ describe "the inline_epp function" do def eval_template_with_args(content, args_hash) - scope.function_inline_epp([content, args_hash]) + epp_function.call(scope, content, args_hash) end def eval_template(content) - scope.function_inline_epp([content]) + epp_function.call(scope, content) end + + def epp_function() + epp_func = scope.compiler.loaders.public_environment_loader.load(:function, 'inline_epp') + end + end diff --git a/spec/unit/functions/map_spec.rb b/spec/unit/functions/map_spec.rb new file mode 100644 index 000000000..e1b09cf24 --- /dev/null +++ b/spec/unit/functions/map_spec.rb @@ -0,0 +1,169 @@ +require 'puppet' +require 'spec_helper' +require 'puppet_spec/compiler' +require 'matchers/resource' + +require 'shared_behaviours/iterative_functions' + +describe 'the map method' do + include PuppetSpec::Compiler + include Matchers::Resource + + 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 + + expect(catalog).to have_resource("File[/file_2]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_4]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_6]").with_parameter(:ensure, 'present') + end + + it 'map on an enumerable type (multiplying each value by 2)' do + catalog = compile_to_catalog(<<-MANIFEST) + $a = Integer[1,3] + $a.map |$x|{ $x*2}.each |$v|{ + file { "/file_$v": ensure => present } + } + MANIFEST + + expect(catalog).to have_resource("File[/file_2]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_4]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_6]").with_parameter(:ensure, 'present') + end + + it 'map on an integer (multiply each by 3)' do + catalog = compile_to_catalog(<<-MANIFEST) + 3.map |$x|{ $x*3}.each |$v|{ + file { "/file_$v": ensure => present } + } + MANIFEST + + expect(catalog).to have_resource("File[/file_0]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_3]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_6]").with_parameter(:ensure, 'present') + end + + it 'map on a string' do + catalog = compile_to_catalog(<<-MANIFEST) + $a = {a=>x, b=>y} + "ab".map |$x|{$a[$x]}.each |$v|{ + file { "/file_$v": ensure => present } + } + MANIFEST + + expect(catalog).to have_resource("File[/file_x]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_y]").with_parameter(:ensure, 'present') + end + + it 'map on an array (multiplying value by 10 in even index position)' do + catalog = compile_to_catalog(<<-MANIFEST) + $a = [1,2,3] + $a.map |$i, $x|{ if $i % 2 == 0 {$x} else {$x*10}}.each |$v|{ + file { "/file_$v": ensure => present } + } + MANIFEST + + expect(catalog).to have_resource("File[/file_1]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_20]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_3]").with_parameter(:ensure, '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 + + expect(catalog).to have_resource("File[/file_a]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_b]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_c]").with_parameter(:ensure, 'present') + end + + it 'map on a hash selecting keys - using two block parameters' do + catalog = compile_to_catalog(<<-MANIFEST) + $a = {'a'=>1,'b'=>2,'c'=>3} + $a.map |$k,$v|{ file { "/file_$k": ensure => present } + } + MANIFEST + + expect(catalog).to have_resource("File[/file_a]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_b]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_c]").with_parameter(:ensure, 'present') + end + + it 'map on a hash using captures-last parameter' do + catalog = compile_to_catalog(<<-MANIFEST) + $a = {'a'=>present,'b'=>absent,'c'=>present} + $a.map |*$kv|{ file { "/file_${kv[0]}": ensure => $kv[1] } } + MANIFEST + + expect(catalog).to have_resource("File[/file_a]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_b]").with_parameter(:ensure, 'absent') + expect(catalog).to have_resource("File[/file_c]").with_parameter(:ensure, '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 + + expect(catalog).to have_resource("File[/file_1]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_2]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_3]").with_parameter(:ensure, 'present') + end + + it 'each on a hash selecting value - using two block parameters' do + catalog = compile_to_catalog(<<-MANIFEST) + $a = {'a'=>1,'b'=>2,'c'=>3} + $a.map |$k,$v|{ file { "/file_$v": ensure => present } } + MANIFEST + + expect(catalog).to have_resource("File[/file_1]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_2]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_3]").with_parameter(:ensure, '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 + + expect(catalog).to have_resource("File[/file_0.false]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_1.false]").with_parameter(:ensure, '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 + + expect(catalog).to have_resource("File[/file_0.]").with_parameter(:ensure, '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/functions/match_spec.rb b/spec/unit/functions/match_spec.rb new file mode 100644 index 000000000..f4e2e383b --- /dev/null +++ b/spec/unit/functions/match_spec.rb @@ -0,0 +1,57 @@ +require 'spec_helper' +require 'puppet/pops' +require 'puppet/loaders' + +describe 'the match function' do + + before(:all) do + loaders = Puppet::Pops::Loaders.new(Puppet::Node::Environment.create(:testing, [])) + Puppet.push_context({:loaders => loaders}, "test-examples") + end + + after(:all) do + Puppet::Pops::Loaders.clear + Puppet::pop_context() + end + + let(:func) do + Puppet.lookup(:loaders).puppet_system_loader.load(:function, 'match') + end + + let(:type_parser) { Puppet::Pops::Types::TypeParser.new } + + + it 'matches string and regular expression without captures' do + expect(func.call({}, 'abc123', /[a-z]+[1-9]+/)).to eql(['abc123']) + end + + it 'matches string and regular expression with captures' do + expect(func.call({}, 'abc123', /([a-z]+)([1-9]+)/)).to eql(['abc123', 'abc', '123']) + end + + it 'produces nil if match is not found' do + expect(func.call({}, 'abc123', /([x]+)([6]+)/)).to be_nil + end + + [ 'Pattern[/([a-z]+)([1-9]+)/]', # regexp + 'Pattern["([a-z]+)([1-9]+)"]', # string + 'Regexp[/([a-z]+)([1-9]+)/]', # regexp type + 'Pattern[/x9/, /([a-z]+)([1-9]+)/]', # regexp, first found matches + ].each do |pattern| + it "matches string and type #{pattern} with captures" do + expect(func.call({}, 'abc123', type(pattern))).to eql(['abc123', 'abc', '123']) + end + end + + it 'matches an array of strings and yields a map of the result' do + expect(func.call({}, ['abc123', '2a', 'xyz2'], /([a-z]+)[1-9]+/)).to eql([['abc123', 'abc'], nil, ['xyz2', 'xyz']]) + end + + it 'raises error if Regexp type without regexp is used' do + expect{func.call({}, 'abc123', type('Regexp'))}.to raise_error(ArgumentError, /Given Regexp Type has no regular expression/) + end + + def type(s) + Puppet::Pops::Types::TypeParser.new.parse(s) + end +end diff --git a/spec/unit/parser/methods/reduce_spec.rb b/spec/unit/functions/reduce_spec.rb index 4f0c14e5e..032f6ccc4 100644 --- a/spec/unit/parser/methods/reduce_spec.rb +++ b/spec/unit/functions/reduce_spec.rb @@ -1,9 +1,12 @@ require 'puppet' require 'spec_helper' require 'puppet_spec/compiler' +require 'matchers/resource' +require 'shared_behaviours/iterative_functions' describe 'the reduce method' do include PuppetSpec::Compiler + include Matchers::Resource before :all do # enable switching back @@ -32,7 +35,17 @@ describe 'the reduce method' do file { "/file_$b": ensure => present } MANIFEST - catalog.resource(:file, "/file_6")['ensure'].should == 'present' + expect(catalog).to have_resource("File[/file_6]").with_parameter(:ensure, 'present') + end + + it 'reduce on an array with captures rest in lambda' do + catalog = compile_to_catalog(<<-MANIFEST) + $a = [1,2,3] + $b = $a.reduce |*$mx| { $mx[0] + $mx[1] } + file { "/file_$b": ensure => present } + MANIFEST + + expect(catalog).to have_resource("File[/file_6]").with_parameter(:ensure, 'present') end it 'reduce on enumerable type' do @@ -42,7 +55,7 @@ describe 'the reduce method' do file { "/file_$b": ensure => present } MANIFEST - catalog.resource(:file, "/file_6")['ensure'].should == 'present' + expect(catalog).to have_resource("File[/file_6]").with_parameter(:ensure, 'present') end it 'reduce on an array with start value' do @@ -52,8 +65,9 @@ describe 'the reduce method' do file { "/file_$b": ensure => present } MANIFEST - catalog.resource(:file, "/file_10")['ensure'].should == 'present' + expect(catalog).to have_resource("File[/file_10]").with_parameter(:ensure, 'present') end + it 'reduce on a hash' do catalog = compile_to_catalog(<<-MANIFEST) $a = {a=>1, b=>2, c=>3} @@ -62,8 +76,9 @@ describe 'the reduce method' do file { "/file_${$b[0]}_${$b[1]}": ensure => present } MANIFEST - catalog.resource(:file, "/file_sum_6")['ensure'].should == 'present' + expect(catalog).to have_resource("File[/file_sum_6]").with_parameter(:ensure, 'present') end + it 'reduce on a hash with start value' do catalog = compile_to_catalog(<<-MANIFEST) $a = {a=>1, b=>2, c=>3} @@ -72,7 +87,10 @@ describe 'the reduce method' do file { "/file_${$b[0]}_${$b[1]}": ensure => present } MANIFEST - catalog.resource(:file, "/file_sum_10")['ensure'].should == 'present' + expect(catalog).to have_resource("File[/file_sum_10]").with_parameter(:ensure, 'present') end end + + it_should_behave_like 'all iterative functions argument checks', 'reduce' + end diff --git a/spec/unit/parser/methods/slice_spec.rb b/spec/unit/functions/slice_spec.rb index 1de1dd0f1..945cae5c7 100644 --- a/spec/unit/parser/methods/slice_spec.rb +++ b/spec/unit/functions/slice_spec.rb @@ -1,10 +1,11 @@ require 'puppet' require 'spec_helper' require 'puppet_spec/compiler' -require 'rubygems' +require 'matchers/resource' describe 'methods' do include PuppetSpec::Compiler + include Matchers::Resource before :all do # enable switching back @@ -36,9 +37,22 @@ describe 'methods' do } 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' + expect(catalog).to have_resource("File[/file_1]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_2]").with_parameter(:ensure, 'absent') + expect(catalog).to have_resource("File[/file_3]").with_parameter(:ensure, 'present') + end + + it 'slice with captures last' do + catalog = compile_to_catalog(<<-MANIFEST) + $a = [1, present, 2, absent, 3, present] + $a.slice(2) |*$kv| { + file { "/file_${$kv[0]}": ensure => $kv[1] } + } + MANIFEST + + expect(catalog).to have_resource("File[/file_1]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_2]").with_parameter(:ensure, 'absent') + expect(catalog).to have_resource("File[/file_3]").with_parameter(:ensure, 'present') end it 'slice with one parameter' do @@ -49,9 +63,9 @@ describe 'methods' do } 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' + expect(catalog).to have_resource("File[/file_1]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_2]").with_parameter(:ensure, 'absent') + expect(catalog).to have_resource("File[/file_3]").with_parameter(:ensure, 'present') end it 'slice with shorter last slice' do @@ -62,8 +76,8 @@ describe 'methods' do } MANIFEST - catalog.resource(:file, "/file_1.2")['ensure'].should == 'present' - catalog.resource(:file, "/file_3.")['ensure'].should == 'absent' + expect(catalog).to have_resource("File[/file_1.2]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_3.]").with_parameter(:ensure, 'absent') end end @@ -76,8 +90,8 @@ describe 'methods' do } MANIFEST - catalog.resource(:file, "/file_1.2")['ensure'].should == 'present' - catalog.resource(:file, "/file_3.")['ensure'].should == 'absent' + expect(catalog).to have_resource("File[/file_1.2]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_3.]").with_parameter(:ensure, 'absent') end end @@ -90,8 +104,8 @@ describe 'methods' do } MANIFEST - catalog.resource(:file, "/file_1.2")['ensure'].should == 'present' - catalog.resource(:file, "/file_3.4")['ensure'].should == 'present' + expect(catalog).to have_resource("File[/file_1.2]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_3.4]").with_parameter(:ensure, 'present') end it 'slice with integer' do @@ -101,8 +115,8 @@ describe 'methods' do } MANIFEST - catalog.resource(:file, "/file_0.1")['ensure'].should == 'present' - catalog.resource(:file, "/file_2.3")['ensure'].should == 'present' + expect(catalog).to have_resource("File[/file_0.1]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_2.3]").with_parameter(:ensure, 'present') end it 'slice with string' do @@ -112,8 +126,8 @@ describe 'methods' do } MANIFEST - catalog.resource(:file, "/file_a.b")['ensure'].should == 'present' - catalog.resource(:file, "/file_c.d")['ensure'].should == 'present' + expect(catalog).to have_resource("File[/file_a.b]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_c.d]").with_parameter(:ensure, 'present') end end @@ -126,10 +140,9 @@ describe 'methods' do } 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' - + expect(catalog).to have_resource("File[/file_1]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_2]").with_parameter(:ensure, 'absent') + expect(catalog).to have_resource("File[/file_3]").with_parameter(:ensure, 'present') end end end diff --git a/spec/unit/functions/with_spec.rb b/spec/unit/functions/with_spec.rb new file mode 100644 index 000000000..952b14412 --- /dev/null +++ b/spec/unit/functions/with_spec.rb @@ -0,0 +1,35 @@ +require 'spec_helper' + +require 'puppet_spec/compiler' +require 'matchers/resource' + +describe 'the with function' do + include PuppetSpec::Compiler + include Matchers::Resource + + before :each do + Puppet[:parser] = 'future' + end + + it 'calls a lambda passing no arguments' do + expect(compile_to_catalog("with() || { notify { testing: } }")).to have_resource('Notify[testing]') + end + + it 'calls a lambda passing a single argument' do + expect(compile_to_catalog('with(1) |$x| { notify { "testing$x": } }')).to have_resource('Notify[testing1]') + end + + it 'calls a lambda passing more than one argument' do + expect(compile_to_catalog('with(1, 2) |*$x| { notify { "testing${x[0]}, ${x[1]}": } }')).to have_resource('Notify[testing1, 2]') + end + + it 'passes a type reference to a lambda' do + expect(compile_to_catalog('notify { test: message => "data" } with(Notify[test]) |$x| { notify { "${x[message]}": } }')).to have_resource('Notify[data]') + end + + it 'errors when not given enough arguments for the lambda' do + expect do + compile_to_catalog('with(1) |$x, $y| { }') + end.to raise_error(/Parameter \$y is required but no value was given/m) + end +end diff --git a/spec/unit/functions4_spec.rb b/spec/unit/functions4_spec.rb index a5404cc1e..6c31b75c2 100644 --- a/spec/unit/functions4_spec.rb +++ b/spec/unit/functions4_spec.rb @@ -83,9 +83,9 @@ actual: func = f.new(:closure_scope, :loader) expect(func.is_a?(Puppet::Functions::Function)).to be_true signature = if RUBY_VERSION =~ /^1\.8/ - 'Object{2}' + 'Any{2}' else - 'Object x, Object y' + 'Any x, Any y' end expect do func.call({}, 10) @@ -102,9 +102,9 @@ actual: func = f.new(:closure_scope, :loader) expect(func.is_a?(Puppet::Functions::Function)).to be_true signature = if RUBY_VERSION =~ /^1\.8/ - 'Object{2}' + 'Any{2}' else - 'Object x, Object y' + 'Any x, Any y' end expect do func.call({}, 10, 10, 10) @@ -162,9 +162,9 @@ actual: func = f.new(:closure_scope, :loader) expect(func.is_a?(Puppet::Functions::Function)).to be_true signature = if RUBY_VERSION =~ /^1\.8/ - 'Object{2,}' + 'Any{2,}' else - 'Object x, Object y, Object a?, Object b?, Object c{0,}' + 'Any x, Any y, Any a?, Any b?, Any c{0,}' end expect do func.call({}, 10) @@ -439,12 +439,11 @@ actual: # about selection of parser and evaluator # Puppet[:parser] = 'future' - Puppet[:evaluator] = 'future' # Puppetx cannot be loaded until the correct parser has been set (injector is turned off otherwise) require 'puppetx' end - let(:parser) { Puppet::Pops::Parser::EvaluatingParser::Transitional.new } + let(:parser) { Puppet::Pops::Parser::EvaluatingParser.new } let(:node) { 'node.example.com' } let(:scope) { s = create_test_scope_for_node(node); s } diff --git a/spec/unit/indirector/catalog/compiler_spec.rb b/spec/unit/indirector/catalog/compiler_spec.rb index 7d92aa80f..4aaecd664 100755 --- a/spec/unit/indirector/catalog/compiler_spec.rb +++ b/spec/unit/indirector/catalog/compiler_spec.rb @@ -6,10 +6,8 @@ require 'puppet/rails' describe Puppet::Resource::Catalog::Compiler do before do - require 'puppet/rails' Puppet::Rails.stubs(:init) Facter.stubs(:to_hash).returns({}) - Facter.stubs(:value).returns(Facter::Util::Fact.new("something")) end describe "when initializing" do diff --git a/spec/unit/indirector/catalog/static_compiler_spec.rb b/spec/unit/indirector/catalog/static_compiler_spec.rb index a3d13e804..556bc7943 100644 --- a/spec/unit/indirector/catalog/static_compiler_spec.rb +++ b/spec/unit/indirector/catalog/static_compiler_spec.rb @@ -11,10 +11,21 @@ describe Puppet::Resource::Catalog::StaticCompiler do end before :each do + Facter.stubs(:loadfacts) Facter.stubs(:to_hash).returns({}) Facter.stubs(:value) end + around(:each) do |example| + Puppet.override({ + :current_environment => Puppet::Node::Environment.create(:app, []), + }, + "Ensure we are using an environment other than root" + ) do + example.run + end + end + let(:request) do Puppet::Indirector::Request.new(:the_indirection_named_foo, :find, diff --git a/spec/unit/indirector/data_binding/hiera_spec.rb b/spec/unit/indirector/data_binding/hiera_spec.rb index 7ab3b27fc..12572b174 100644 --- a/spec/unit/indirector/data_binding/hiera_spec.rb +++ b/spec/unit/indirector/data_binding/hiera_spec.rb @@ -2,34 +2,6 @@ require 'spec_helper' require 'puppet/indirector/data_binding/hiera' describe Puppet::DataBinding::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', '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 Puppet::DataBinding::Hiera.doc.should_not be_nil end @@ -42,73 +14,6 @@ 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 + it_should_behave_like "Hiera indirection", Puppet::DataBinding::Hiera, my_fixture_dir end diff --git a/spec/unit/indirector/facts/facter_spec.rb b/spec/unit/indirector/facts/facter_spec.rb index cb90fb7c3..4417ede54 100755 --- a/spec/unit/indirector/facts/facter_spec.rb +++ b/spec/unit/indirector/facts/facter_spec.rb @@ -1,9 +1,8 @@ #! /usr/bin/env ruby require 'spec_helper' - require 'puppet/indirector/facts/facter' -module PuppetNodeFactsFacter +module NodeFactsFacterSpec describe Puppet::Node::Facts::Facter do FS = Puppet::FileSystem @@ -24,27 +23,6 @@ describe Puppet::Node::Facts::Facter do Puppet::Node::Facts::Facter.name.should == :facter end - describe "when reloading Facter" do - before do - @facter_class = Puppet::Node::Facts::Facter - Facter.stubs(:clear) - Facter.stubs(:load) - Facter.stubs(:loadfacts) - end - - it "should clear Facter" do - Facter.expects(:clear) - @facter_class.reload_facter - end - - it "should load all Facter facts" do - Facter.expects(:loadfacts) - @facter_class.reload_facter - end - end -end - -describe Puppet::Node::Facts::Facter do before :each do Puppet::Node::Facts::Facter.stubs(:reload_facter) @facter = Puppet::Node::Facts::Facter.new @@ -54,22 +32,31 @@ describe Puppet::Node::Facts::Facter do @environment = stub 'environment' @request.stubs(:environment).returns(@environment) @request.environment.stubs(:modules).returns([]) + @request.environment.stubs(:modulepath).returns([]) end - describe Puppet::Node::Facts::Facter, " when finding facts" do - it "should reset and load facts" do - clear = sequence 'clear' - Puppet::Node::Facts::Facter.expects(:reload_facter).in_sequence(clear) - Puppet::Node::Facts::Facter.expects(:load_fact_plugins).in_sequence(clear) + describe 'when finding facts' do + it 'should reset facts' do + reset = sequence 'reset' + Facter.expects(:reset).in_sequence(reset) + Puppet::Node::Facts::Facter.expects(:setup_search_paths).in_sequence(reset) + @facter.find(@request) + end + + it 'should include external facts when feature is present' do + reset = sequence 'reset' + Puppet.features.stubs(:external_facts?).returns true + Facter.expects(:reset).in_sequence(reset) + Puppet::Node::Facts::Facter.expects(:setup_external_search_paths).in_sequence(reset) + Puppet::Node::Facts::Facter.expects(:setup_search_paths).in_sequence(reset) @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) + it 'should not include external facts when feature is not present' do + reset = sequence 'reset' + Puppet.features.stubs(:external_facts?).returns false + Facter.expects(:reset).in_sequence(reset) + Puppet::Node::Facts::Facter.expects(:setup_search_paths).in_sequence(reset) @facter.find(@request) end @@ -114,89 +101,69 @@ describe Puppet::Node::Facts::Facter do end end - describe Puppet::Node::Facts::Facter, " when saving facts" do - - it "should fail" do - proc { @facter.save(@facts) }.should raise_error(Puppet::DevError) - end - end - - describe Puppet::Node::Facts::Facter, " when destroying facts" do - - it "should fail" do - proc { @facter.destroy(@facts) }.should raise_error(Puppet::DevError) - end + it 'should fail when saving facts' do + proc { @facter.save(@facts) }.should raise_error(Puppet::DevError) end - it "should skip files when asked to load a directory" do - FileTest.expects(:directory?).with("myfile").returns false - - Puppet::Node::Facts::Facter.load_facts_in_dir("myfile") + it 'should fail when destroying facts' do + proc { @facter.destroy(@facts) }.should raise_error(Puppet::DevError) end - it "should load each ruby file when asked to load a directory" do - FileTest.expects(:directory?).with("mydir").returns true - Dir.expects(:chdir).with("mydir").yields + describe 'when setting up search paths' do + let(:factpath1) { File.expand_path 'one' } + let(:factpath2) { File.expand_path 'two' } + let(:factpath) { [factpath1, factpath2].join(File::PATH_SEPARATOR) } + let(:modulepath) { File.expand_path 'module/foo' } + let(:modulelibfacter) { File.expand_path 'module/foo/lib/facter' } + let(:modulepluginsfacter) { File.expand_path 'module/foo/plugins/facter' } - Dir.expects(:glob).with("*.rb").returns %w{a.rb b.rb} + before :each do + FileTest.expects(:directory?).with(factpath1).returns true + FileTest.expects(:directory?).with(factpath2).returns true + @request.environment.stubs(:modulepath).returns [modulepath] + Dir.expects(:glob).with("#{modulepath}/*/lib/facter").returns [modulelibfacter] + Dir.expects(:glob).with("#{modulepath}/*/plugins/facter").returns [modulepluginsfacter] - Puppet::Node::Facts::Facter.expects(:load).with("a.rb") - Puppet::Node::Facts::Facter.expects(:load).with("b.rb") - - Puppet::Node::Facts::Facter.load_facts_in_dir("mydir") - end + Puppet[:factpath] = factpath + end - it "should include pluginfactdest when loading external facts", - :if => (Puppet.features.external_facts? and not Puppet.features.microsoft_windows?) do - Puppet[:pluginfactdest] = "/plugin/dest" - @facter.find(@request) - Facter.search_external_path.include?("/plugin/dest") - end + it 'should skip files' do + FileTest.expects(:directory?).with(modulelibfacter).returns false + FileTest.expects(:directory?).with(modulepluginsfacter).returns false + Facter.expects(:search).with(factpath1, factpath2) + Puppet::Node::Facts::Facter.setup_search_paths @request + end - it "should include pluginfactdest when loading external facts", - :if => (Puppet.features.external_facts? and Puppet.features.microsoft_windows?) do - Puppet[:pluginfactdest] = "/plugin/dest" - @facter.find(@request) - Facter.search_external_path.include?("C:/plugin/dest") + it 'should add directories' do + FileTest.expects(:directory?).with(modulelibfacter).returns true + FileTest.expects(:directory?).with(modulepluginsfacter).returns true + Facter.expects(:search).with(modulelibfacter, modulepluginsfacter, factpath1, factpath2) + Puppet::Node::Facts::Facter.setup_search_paths @request + end end - describe "when loading fact plugins from disk" do - let(:one) { File.expand_path("one") } - let(:two) { File.expand_path("two") } - - it "should load each directory in the Fact path" do - Puppet[:factpath] = [one, two].join(File::PATH_SEPARATOR) - - Puppet::Node::Facts::Facter.expects(:load_facts_in_dir).with(one) - Puppet::Node::Facts::Facter.expects(:load_facts_in_dir).with(two) + describe 'when setting up external search paths', :if => Puppet.features.external_facts? do + let(:pluginfactdest) { File.expand_path 'plugin/dest' } + let(:modulepath) { File.expand_path 'module/foo' } + let(:modulefactsd) { File.expand_path 'module/foo/facts.d' } - Puppet::Node::Facts::Facter.load_fact_plugins + before :each do + FileTest.expects(:directory?).with(pluginfactdest).returns true + mod = Puppet::Module.new('foo', modulepath, @request.environment) + @request.environment.stubs(:modules).returns [mod] + Puppet[:pluginfactdest] = pluginfactdest end - it "should load all facts from the modules" do - Puppet::Node::Facts::Facter.stubs(:load_facts_in_dir) - - Dir.stubs(:glob).returns [] - Dir.expects(:glob).with("#{one}/*/lib/facter").returns %w{oneA oneB} - Dir.expects(:glob).with("#{two}/*/lib/facter").returns %w{twoA twoB} - - Puppet::Node::Facts::Facter.expects(:load_facts_in_dir).with("oneA") - Puppet::Node::Facts::Facter.expects(:load_facts_in_dir).with("oneB") - Puppet::Node::Facts::Facter.expects(:load_facts_in_dir).with("twoA") - Puppet::Node::Facts::Facter.expects(:load_facts_in_dir).with("twoB") - - FS.overlay(FS::MemoryFile.a_directory(one), FS::MemoryFile.a_directory(two)) do - Puppet.override(:current_environment => Puppet::Node::Environment.create(:testing, [one, two], "")) do - Puppet::Node::Facts::Facter.load_fact_plugins - end - end + it 'should skip files' do + File.expects(:directory?).with(modulefactsd).returns false + Facter.expects(:search_external).with [pluginfactdest] + Puppet::Node::Facts::Facter.setup_external_search_paths @request 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.search_external_path.include?("#{one}/mymodule/facts.d") + it 'should add directories' do + File.expects(:directory?).with(modulefactsd).returns true + Facter.expects(:search_external).with [modulefactsd, pluginfactdest] + Puppet::Node::Facts::Facter.setup_external_search_paths @request end end end diff --git a/spec/unit/indirector/hiera_spec.rb b/spec/unit/indirector/hiera_spec.rb new file mode 100644 index 000000000..f74fd29aa --- /dev/null +++ b/spec/unit/indirector/hiera_spec.rb @@ -0,0 +1,17 @@ +require 'spec_helper' +require 'puppet/data_binding' +require 'puppet/indirector/hiera' +require 'hiera/backend' + +describe Puppet::Indirector::Hiera do + + module Testing + module DataBinding + class Hiera < Puppet::Indirector::Hiera + end + end + end + + it_should_behave_like "Hiera indirection", Testing::DataBinding::Hiera, my_fixture_dir +end + diff --git a/spec/unit/indirector/request_spec.rb b/spec/unit/indirector/request_spec.rb index 3c11664d3..e3edfd670 100755 --- a/spec/unit/indirector/request_spec.rb +++ b/spec/unit/indirector/request_spec.rb @@ -232,14 +232,12 @@ describe Puppet::Indirector::Request do Puppet::Indirector::Request.new(:myind, :find, "my key", nil, :environment => env).environment.should equal(env) end - it "should use the configured environment when none is provided" do + it "should use the current environment when none is provided" do configured = Puppet::Node::Environment.create(:foo, []) Puppet[:environment] = "foo" - Puppet.override(:environments => Puppet::Environments::Static.new(configured)) do - Puppet::Indirector::Request.new(:myind, :find, "my key", nil).environment.should == configured - end + expect(Puppet::Indirector::Request.new(:myind, :find, "my key", nil).environment).to eq(Puppet.lookup(:current_environment)) end it "should support converting its options to a hash" do diff --git a/spec/unit/indirector/resource/ral_spec.rb b/spec/unit/indirector/resource/ral_spec.rb index 8b42f6881..36ba5f1c2 100755 --- a/spec/unit/indirector/resource/ral_spec.rb +++ b/spec/unit/indirector/resource/ral_spec.rb @@ -25,6 +25,11 @@ describe "Puppet::Resource::Ral" do Puppet::Resource::Ral.new.find(@request).should == my_resource end + it "should produce Puppet::Error instead of ArgumentError" do + @bad_request = stub 'thiswillcauseanerror', :key => "thiswill/causeanerror" + expect{Puppet::Resource::Ral.new.find(@bad_request)}.to raise_error(Puppet::Error) + end + it "if there is no instance, it should create one" do wrong_instance = stub "wrong user", :name => "bob" root = mock "Root User" diff --git a/spec/unit/indirector/resource_type/parser_spec.rb b/spec/unit/indirector/resource_type/parser_spec.rb index cf81428e4..fdbab84e7 100755 --- a/spec/unit/indirector/resource_type/parser_spec.rb +++ b/spec/unit/indirector/resource_type/parser_spec.rb @@ -7,9 +7,13 @@ require 'puppet_spec/files' describe Puppet::Indirector::ResourceType::Parser do include PuppetSpec::Files + let(:environmentpath) { tmpdir("envs") } + let(:modulepath) { "#{environmentpath}/test/modules" } + let(:environment) { Puppet::Node::Environment.create(:test, [modulepath]) } before do @terminus = Puppet::Indirector::ResourceType::Parser.new @request = Puppet::Indirector::Request.new(:resource_type, :find, "foo", nil) + @request.environment = environment @krt = @request.environment.known_resource_types end @@ -26,19 +30,18 @@ describe Puppet::Indirector::ResourceType::Parser do end it "should attempt to load the type if none is found in memory" do - dir = tmpdir("find_a_type") - FileUtils.mkdir_p(dir) - Puppet[:modulepath] = dir + FileUtils.mkdir_p(modulepath) # Make a new request, since we've reset the env - @request = Puppet::Indirector::Request.new(:resource_type, :find, "foo::bar", nil) + request = Puppet::Indirector::Request.new(:resource_type, :find, "foo::bar", nil) + request.environment = environment - manifest_path = File.join(dir, "foo", "manifests") + manifest_path = File.join(modulepath, "foo", "manifests") FileUtils.mkdir_p(manifest_path) File.open(File.join(manifest_path, "bar.pp"), "w") { |f| f.puts "class foo::bar {}" } - result = @terminus.find(@request) + result = @terminus.find(request) result.should be_instance_of(Puppet::Resource::Type) result.name.should == "foo::bar" end @@ -108,10 +111,11 @@ describe Puppet::Indirector::ResourceType::Parser do second = File.join(dir, "second") FileUtils.mkdir_p(first) FileUtils.mkdir_p(second) - Puppet[:modulepath] = "#{first}#{File::PATH_SEPARATOR}#{second}" + environment = Puppet::Node::Environment.create(:test, [first, second]) # Make a new request, since we've reset the env - @request = Puppet::Indirector::Request.new(:resource_type, :search, "*", nil) + request = Puppet::Indirector::Request.new(:resource_type, :search, "*", nil) + request.environment = environment onepath = File.join(first, "one", "manifests") FileUtils.mkdir_p(onepath) @@ -121,7 +125,7 @@ describe Puppet::Indirector::ResourceType::Parser do File.open(File.join(onepath, "oneklass.pp"), "w") { |f| f.puts "class one::oneklass {}" } File.open(File.join(twopath, "twoklass.pp"), "w") { |f| f.puts "class two::twoklass {}" } - result = @terminus.search(@request) + result = @terminus.search(request) result.find { |t| t.name == "one::oneklass" }.should be_instance_of(Puppet::Resource::Type) result.find { |t| t.name == "two::twoklass" }.should be_instance_of(Puppet::Resource::Type) end @@ -228,10 +232,11 @@ describe Puppet::Indirector::ResourceType::Parser do second = File.join(dir, "second") FileUtils.mkdir_p(first) FileUtils.mkdir_p(second) - Puppet[:modulepath] = "#{first}#{File::PATH_SEPARATOR}#{second}" + environment = Puppet::Node::Environment.create(:test, [first,second]) # Make a new request, since we've reset the env - @request = Puppet::Indirector::Request.new(:resource_type, :search, "*", nil) + request = Puppet::Indirector::Request.new(:resource_type, :search, "*", nil) + request.environment = environment onepath = File.join(first, "one", "manifests") FileUtils.mkdir_p(onepath) @@ -241,7 +246,7 @@ describe Puppet::Indirector::ResourceType::Parser do File.open(File.join(onepath, "oneklass.pp"), "w") { |f| f.puts "class one::oneklass {}" } File.open(File.join(twopath, "twoklass.pp"), "w") { |f| f.puts "class two::twoklass {}" } - result = @terminus.search(@request) + result = @terminus.search(request) result.find { |t| t.name == "one::oneklass" }.should be_instance_of(Puppet::Resource::Type) result.find { |t| t.name == "two::twoklass" }.should be_instance_of(Puppet::Resource::Type) end diff --git a/spec/unit/indirector/rest_spec.rb b/spec/unit/indirector/rest_spec.rb index 5c9065fec..7a97708d3 100755 --- a/spec/unit/indirector/rest_spec.rb +++ b/spec/unit/indirector/rest_spec.rb @@ -136,6 +136,12 @@ describe Puppet::Indirector::REST do let(:indirection) { Puppet::TestModel.indirection } let(:model) { Puppet::TestModel } + around(:each) do |example| + Puppet.override(:current_environment => Puppet::Node::Environment.create(:production, [])) do + example.run + end + end + def mock_response(code, body, content_type='text/plain', encoding=nil) obj = stub('http 200 ok', :code => code.to_s, :body => body) obj.stubs(:[]).with('content-type').returns(content_type) @@ -285,17 +291,41 @@ describe Puppet::Indirector::REST do end context 'when fail_on_404 is used in request' do - let(:request) { find_request('foo', :fail_on_404 => true) } - it 'raises an error for a 404 when asked to do so' do + request = find_request('foo', :fail_on_404 => true) response = mock_response('404', 'this is the notfound you are looking for') connection.expects(:get).returns(response) - expected_message = [ - 'Find /production/test_model/foo?fail_on_404=true', - 'resulted in 404 with the message: this is the notfound you are looking for'].join( ' ') + + expect do + terminus.find(request) + end.to raise_error( + Puppet::Error, + 'Find /production/test_model/foo?fail_on_404=true resulted in 404 with the message: this is the notfound you are looking for') + end + + it 'truncates the URI when it is very long' do + request = find_request('foo', :fail_on_404 => true, :long_param => ('A' * 100) + 'B') + response = mock_response('404', 'this is the notfound you are looking for') + connection.expects(:get).returns(response) + + expect do + terminus.find(request) + end.to raise_error( + Puppet::Error, + /\/production\/test_model\/foo.*long_param=A+\.\.\..*resulted in 404 with the message/) + end + + it 'does not truncate the URI when logging debug information' do + Puppet.debug = true + request = find_request('foo', :fail_on_404 => true, :long_param => ('A' * 100) + 'B') + response = mock_response('404', 'this is the notfound you are looking for') + connection.expects(:get).returns(response) + expect do terminus.find(request) - end.to raise_error(Puppet::Error, expected_message) + end.to raise_error( + Puppet::Error, + /\/production\/test_model\/foo.*long_param=A+B.*resulted in 404 with the message/) end end diff --git a/spec/unit/interface/face_collection_spec.rb b/spec/unit/interface/face_collection_spec.rb index 0562c933e..89928b70a 100755 --- a/spec/unit/interface/face_collection_spec.rb +++ b/spec/unit/interface/face_collection_spec.rb @@ -11,12 +11,12 @@ describe Puppet::Interface::FaceCollection do # the 'subject' of the specs will differ. before :all do # Save FaceCollection's global state - faces = subject.instance_variable_get(:@faces) + faces = described_class.instance_variable_get(:@faces) @faces = faces.dup faces.each do |k, v| @faces[k] = v.dup end - @faces_loaded = subject.instance_variable_get(:@loaded) + @faces_loaded = described_class.instance_variable_get(:@loaded) # Save the already required face files @required = [] diff --git a/spec/unit/module_tool/applications/builder_spec.rb b/spec/unit/module_tool/applications/builder_spec.rb index e2b63c192..291473db9 100644 --- a/spec/unit/module_tool/applications/builder_spec.rb +++ b/spec/unit/module_tool/applications/builder_spec.rb @@ -1,4 +1,5 @@ require 'spec_helper' +require 'puppet/file_system' require 'puppet/module_tool/applications' require 'puppet_spec/modules' @@ -12,6 +13,330 @@ describe Puppet::ModuleTool::Applications::Builder do let(:tarball) { File.join(path, 'pkg', release_name) + ".tar.gz" } let(:builder) { Puppet::ModuleTool::Applications::Builder.new(path) } + shared_examples "a packagable module" do + def target_exists?(file) + File.exist?(File.join(path, "pkg", "#{module_name}-#{version}", file)) + end + + def build + tarrer = mock('tarrer') + Puppet::ModuleTool::Tar.expects(:instance).returns(tarrer) + Dir.expects(:chdir).with(File.join(path, 'pkg')).yields + tarrer.expects(:pack).with(release_name, tarball) + + builder.run + end + + def create_regular_files + Puppet::FileSystem.touch(File.join(path, '.dotfile')) + Puppet::FileSystem.touch(File.join(path, 'file.foo')) + Puppet::FileSystem.touch(File.join(path, 'REVISION')) + Puppet::FileSystem.touch(File.join(path, '~file')) + Puppet::FileSystem.touch(File.join(path, '#file')) + Puppet::FileSystem.mkpath(File.join(path, 'pkg')) + Puppet::FileSystem.mkpath(File.join(path, 'coverage')) + Puppet::FileSystem.mkpath(File.join(path, 'sub')) + Puppet::FileSystem.touch(File.join(path, 'sub/.dotfile')) + Puppet::FileSystem.touch(File.join(path, 'sub/file.foo')) + Puppet::FileSystem.touch(File.join(path, 'sub/REVISION')) + Puppet::FileSystem.touch(File.join(path, 'sub/~file')) + Puppet::FileSystem.touch(File.join(path, 'sub/#file')) + Puppet::FileSystem.mkpath(File.join(path, 'sub/pkg')) + Puppet::FileSystem.mkpath(File.join(path, 'sub/coverage')) + end + + def create_symlinks + Puppet::FileSystem.touch(File.join(path, 'symlinkedfile')) + Puppet::FileSystem.symlink(File.join(path, 'symlinkedfile'), File.join(path, 'symlinkfile')) + end + + def create_ignored_files + Puppet::FileSystem.touch(File.join(path, 'gitignored.foo')) + Puppet::FileSystem.mkpath(File.join(path, 'gitdirectory/sub')) + Puppet::FileSystem.touch(File.join(path, 'gitdirectory/gitartifact')) + Puppet::FileSystem.touch(File.join(path, 'gitdirectory/gitimportantfile')) + Puppet::FileSystem.touch(File.join(path, 'gitdirectory/sub/artifact')) + Puppet::FileSystem.touch(File.join(path, 'pmtignored.foo')) + Puppet::FileSystem.mkpath(File.join(path, 'pmtdirectory/sub')) + Puppet::FileSystem.touch(File.join(path, 'pmtdirectory/pmtimportantfile')) + Puppet::FileSystem.touch(File.join(path, 'pmtdirectory/pmtartifact')) + Puppet::FileSystem.touch(File.join(path, 'pmtdirectory/sub/artifact')) + end + + def create_pmtignore_file + Puppet::FileSystem.open(File.join(path, '.pmtignore'), 0600, 'w') do |f| + f << <<-PMTIGNORE +pmtignored.* +pmtdirectory/sub/** +pmtdirectory/pmt* +!pmtimportantfile +PMTIGNORE + end + end + + def create_gitignore_file + Puppet::FileSystem.open(File.join(path, '.gitignore'), 0600, 'w') do |f| + f << <<-GITIGNORE +gitignored.* +gitdirectory/sub/** +gitdirectory/git* +!gitimportantfile +GITIGNORE + end + end + + def create_symlink_gitignore_file + Puppet::FileSystem.open(File.join(path, '.gitignore'), 0600, 'w') do |f| + f << <<-GITIGNORE +symlinkfile + GITIGNORE + end + end + + shared_examples "regular files are present" do + it "has metadata" do + expect(target_exists?('metadata.json')).to eq true + end + + it "has checksums" do + expect(target_exists?('checksums.json')).to eq true + end + + it "copies regular files" do + expect(target_exists?('file.foo')).to eq true + end + end + + shared_examples "default artifacts are removed in module dir but not in subdirs" do + it "ignores dotfiles" do + expect(target_exists?('.dotfile')).to eq false + expect(target_exists?('sub/.dotfile')).to eq true + end + + it "does not have .gitignore" do + expect(target_exists?('.gitignore')).to eq false + end + + it "does not have .pmtignore" do + expect(target_exists?('.pmtignore')).to eq false + end + + it "does not have pkg" do + expect(target_exists?('pkg')).to eq false + expect(target_exists?('sub/pkg')).to eq true + end + + it "does not have coverage" do + expect(target_exists?('coverage')).to eq false + expect(target_exists?('sub/coverage')).to eq true + end + + it "does not have REVISION" do + expect(target_exists?('REVISION')).to eq false + expect(target_exists?('sub/REVISION')).to eq true + end + + it "does not have ~files" do + expect(target_exists?('~file')).to eq false + expect(target_exists?('sub/~file')).to eq true + end + + it "does not have #files" do + expect(target_exists?('#file')).to eq false + expect(target_exists?('sub/#file')).to eq true + end + end + + shared_examples "gitignored files are present" do + it "leaves regular files" do + expect(target_exists?('gitignored.foo')).to eq true + end + + it "leaves directories" do + expect(target_exists?('gitdirectory')).to eq true + end + + it "leaves files in directories" do + expect(target_exists?('gitdirectory/gitartifact')).to eq true + end + + it "leaves exceptional files" do + expect(target_exists?('gitdirectory/gitimportantfile')).to eq true + end + + it "leaves subdirectories" do + expect(target_exists?('gitdirectory/sub')).to eq true + end + + it "leaves files in subdirectories" do + expect(target_exists?('gitdirectory/sub/artifact')).to eq true + end + end + + shared_examples "gitignored files are not present" do + it "ignores regular files" do + expect(target_exists?('gitignored.foo')).to eq false + end + + it "ignores directories" do + expect(target_exists?('gitdirectory')).to eq true + end + + it "ignores files in directories" do + expect(target_exists?('gitdirectory/gitartifact')).to eq false + end + + it "copies exceptional files" do + expect(target_exists?('gitdirectory/gitimportantfile')).to eq true + end + + it "ignores subdirectories" do + expect(target_exists?('gitdirectory/sub')).to eq false + end + + it "ignores files in subdirectories" do + expect(target_exists?('gitdirectory/sub/artifact')).to eq false + end + end + + shared_examples "pmtignored files are present" do + it "leaves regular files" do + expect(target_exists?('pmtignored.foo')).to eq true + end + + it "leaves directories" do + expect(target_exists?('pmtdirectory')).to eq true + end + + it "ignores files in directories" do + expect(target_exists?('pmtdirectory/pmtartifact')).to eq true + end + + it "leaves exceptional files" do + expect(target_exists?('pmtdirectory/pmtimportantfile')).to eq true + end + + it "leaves subdirectories" do + expect(target_exists?('pmtdirectory/sub')).to eq true + end + + it "leaves files in subdirectories" do + expect(target_exists?('pmtdirectory/sub/artifact')).to eq true + end + end + + shared_examples "pmtignored files are not present" do + it "ignores regular files" do + expect(target_exists?('pmtignored.foo')).to eq false + end + + it "ignores directories" do + expect(target_exists?('pmtdirectory')).to eq true + end + + it "copies exceptional files" do + expect(target_exists?('pmtdirectory/pmtimportantfile')).to eq true + end + + it "ignores files in directories" do + expect(target_exists?('pmtdirectory/pmtartifact')).to eq false + end + + it "ignores subdirectories" do + expect(target_exists?('pmtdirectory/sub')).to eq false + end + + it "ignores files in subdirectories" do + expect(target_exists?('pmtdirectory/sub/artifact')).to eq false + end + end + + context "with no ignore files" do + before :each do + create_regular_files + create_ignored_files + + build + end + + it_behaves_like "regular files are present" + it_behaves_like "default artifacts are removed in module dir but not in subdirs" + it_behaves_like "pmtignored files are present" + it_behaves_like "gitignored files are present" + end + + context "with .gitignore file" do + before :each do + create_regular_files + create_ignored_files + create_gitignore_file + + build + end + + it_behaves_like "regular files are present" + it_behaves_like "default artifacts are removed in module dir but not in subdirs" + it_behaves_like "pmtignored files are present" + it_behaves_like "gitignored files are not present" + end + + context "with .pmtignore file" do + before :each do + create_regular_files + create_ignored_files + create_pmtignore_file + + build + end + + it_behaves_like "regular files are present" + it_behaves_like "default artifacts are removed in module dir but not in subdirs" + it_behaves_like "gitignored files are present" + it_behaves_like "pmtignored files are not present" + end + + context "with .pmtignore and .gitignore file" do + before :each do + create_regular_files + create_ignored_files + create_pmtignore_file + create_gitignore_file + + build + end + + it_behaves_like "regular files are present" + it_behaves_like "default artifacts are removed in module dir but not in subdirs" + it_behaves_like "gitignored files are present" + it_behaves_like "pmtignored files are not present" + end + + context "with unignored symlinks", :if => Puppet.features.manages_symlinks? do + before :each do + create_regular_files + create_symlinks + create_ignored_files + end + + it "give an error about symlinks" do + expect { builder.run }.to raise_error + end + end + + context "with .gitignore file and ignored symlinks", :if => Puppet.features.manages_symlinks? do + before :each do + create_regular_files + create_symlinks + create_ignored_files + create_symlink_gitignore_file + end + + it "does not give an error about symlinks" do + expect { build }.not_to raise_error + end + end + end + context 'with metadata.json' do before :each do File.open(File.join(path, 'metadata.json'), 'w') do |f| @@ -28,16 +353,48 @@ describe Puppet::ModuleTool::Applications::Builder do end end - it "packages the module in a tarball named after the module" do - tarrer = mock('tarrer') - Puppet::ModuleTool::Tar.expects(:instance).returns(tarrer) - Dir.expects(:chdir).with(File.join(path, 'pkg')).yields - tarrer.expects(:pack).with(release_name, tarball) + it_behaves_like "a packagable module" - builder.run + it "does not package with a symlink", :if => Puppet.features.manages_symlinks? do + FileUtils.touch(File.join(path, 'tempfile')) + Puppet::FileSystem.symlink(File.join(path, 'tempfile'), File.join(path, 'tempfile2')) + + expect { + builder.run + }.to raise_error Puppet::ModuleTool::Errors::ModuleToolError, /symlinks/i + end + + it "does not package with a symlink in a subdir", :if => Puppet.features.manages_symlinks? do + FileUtils.mkdir(File.join(path, 'manifests')) + FileUtils.touch(File.join(path, 'manifests/tempfile.pp')) + Puppet::FileSystem.symlink(File.join(path, 'manifests/tempfile.pp'), File.join(path, 'manifests/tempfile2.pp')) + + expect { + builder.run + }.to raise_error Puppet::ModuleTool::Errors::ModuleToolError, /symlinks/i end end + context 'with metadata.json containing checksums' do + before :each do + File.open(File.join(path, 'metadata.json'), 'w') do |f| + f.puts({ + "name" => "#{module_name}", + "version" => "#{version}", + "source" => "http://github.com/testing/#{module_name}", + "author" => "testing", + "license" => "Apache License Version 2.0", + "summary" => "Puppet testing module", + "description" => "This module can be used for basic testing", + "project_page" => "http://github.com/testing/#{module_name}", + "checksums" => {"README.md" => "deadbeef"} + }.to_json) + end + end + + it_behaves_like "a packagable module" + end + context 'with Modulefile' do before :each do File.open(File.join(path, 'Modulefile'), 'w') do |f| @@ -54,13 +411,6 @@ MODULEFILE end end - it "packages the module in a tarball named after the module" do - tarrer = mock('tarrer') - Puppet::ModuleTool::Tar.expects(:instance).returns(tarrer) - Dir.expects(:chdir).with(File.join(path, 'pkg')).yields - tarrer.expects(:pack).with(release_name, tarball) - - builder.run - end + it_behaves_like "a packagable module" end end diff --git a/spec/unit/module_tool/applications/uninstaller_spec.rb b/spec/unit/module_tool/applications/uninstaller_spec.rb index 2a8562ab9..66e71b638 100644 --- a/spec/unit/module_tool/applications/uninstaller_spec.rb +++ b/spec/unit/module_tool/applications/uninstaller_spec.rb @@ -113,6 +113,28 @@ describe Puppet::ModuleTool::Applications::Uninstaller do end end + context 'with --ignore-changes' do + def options + super.merge(:ignore_changes => true) + end + + context 'with local changes' do + before do + mark_changed(File.join(primary_dir, 'stdlib')) + end + + it 'overwrites the installed module with the greatest version matching that range' do + subject.should include :result => :success + end + end + + context 'without local changes' do + it 'overwrites the installed module with the greatest version matching that range' do + subject.should include :result => :success + end + end + end + context "when using the --force flag" do def options diff --git a/spec/unit/module_tool/applications/unpacker_spec.rb b/spec/unit/module_tool/applications/unpacker_spec.rb index 39b0c261f..81557df99 100644 --- a/spec/unit/module_tool/applications/unpacker_spec.rb +++ b/spec/unit/module_tool/applications/unpacker_spec.rb @@ -2,6 +2,7 @@ require 'spec_helper' require 'json' require 'puppet/module_tool/applications' +require 'puppet/file_system' require 'puppet_spec/modules' describe Puppet::ModuleTool::Applications::Unpacker do @@ -31,4 +32,43 @@ describe Puppet::ModuleTool::Applications::Unpacker do Puppet::ModuleTool::Applications::Unpacker.run(filename, :target_dir => target) File.should be_directory(File.join(target, 'mytarball')) end + + it "should warn about symlinks", :if => Puppet.features.manages_symlinks? do + untar = mock('Tar') + untar.expects(:unpack).with(filename, anything()) do |src, dest, _| + FileUtils.mkdir(File.join(dest, 'extractedmodule')) + File.open(File.join(dest, 'extractedmodule', 'metadata.json'), 'w+') do |file| + file.puts JSON.generate('name' => module_name, 'version' => '1.0.0') + end + FileUtils.touch(File.join(dest, 'extractedmodule/tempfile')) + Puppet::FileSystem.symlink(File.join(dest, 'extractedmodule/tempfile'), File.join(dest, 'extractedmodule/tempfile2')) + true + end + + Puppet::ModuleTool::Tar.expects(:instance).returns(untar) + Puppet.expects(:warning).with(regexp_matches(/symlinks/i)) + + Puppet::ModuleTool::Applications::Unpacker.run(filename, :target_dir => target) + File.should be_directory(File.join(target, 'mytarball')) + end + + it "should warn about symlinks in subdirectories", :if => Puppet.features.manages_symlinks? do + untar = mock('Tar') + untar.expects(:unpack).with(filename, anything()) do |src, dest, _| + FileUtils.mkdir(File.join(dest, 'extractedmodule')) + File.open(File.join(dest, 'extractedmodule', 'metadata.json'), 'w+') do |file| + file.puts JSON.generate('name' => module_name, 'version' => '1.0.0') + end + FileUtils.mkdir(File.join(dest, 'extractedmodule/manifests')) + FileUtils.touch(File.join(dest, 'extractedmodule/manifests/tempfile')) + Puppet::FileSystem.symlink(File.join(dest, 'extractedmodule/manifests/tempfile'), File.join(dest, 'extractedmodule/manifests/tempfile2')) + true + end + + Puppet::ModuleTool::Tar.expects(:instance).returns(untar) + Puppet.expects(:warning).with(regexp_matches(/symlinks/i)) + + Puppet::ModuleTool::Applications::Unpacker.run(filename, :target_dir => target) + File.should be_directory(File.join(target, 'mytarball')) + end end diff --git a/spec/unit/module_tool/applications/upgrader_spec.rb b/spec/unit/module_tool/applications/upgrader_spec.rb index 44627f94f..382e45a75 100644 --- a/spec/unit/module_tool/applications/upgrader_spec.rb +++ b/spec/unit/module_tool/applications/upgrader_spec.rb @@ -52,6 +52,16 @@ describe Puppet::ModuleTool::Applications::Upgrader do end context 'for an installed module' do + context 'with only one version' do + before { preinstall('puppetlabs-oneversion', '0.0.1') } + let(:module) { 'puppetlabs-oneversion' } + + it 'declines to upgrade' do + subject.should include :result => :noop + subject[:error][:multiline].should =~ /already the latest version/ + end + end + context 'without dependencies' do before { preinstall('pmtacceptance-stdlib', '1.0.0') } @@ -90,6 +100,7 @@ describe Puppet::ModuleTool::Applications::Upgrader do context 'without options' do it 'declines to upgrade' do subject.should include :result => :noop + subject[:error][:multiline].should =~ /already the latest version/ end end @@ -165,6 +176,17 @@ describe Puppet::ModuleTool::Applications::Upgrader do subject.should include :result => :failure subject[:error].should include :oneline => "Could not upgrade '#{self.module}'; module has had changes made locally" end + + context 'with --ignore-changes' do + def options + super.merge(:ignore_changes => true) + end + + it 'overwrites the installed module with the greatest version matching that range' do + subject.should include :result => :success + graph_should_include 'pmtacceptance-stdlib', v('1.0.0') => v('4.1.0') + end + end end context 'with dependencies' do diff --git a/spec/unit/module_tool/installed_modules_spec.rb b/spec/unit/module_tool/installed_modules_spec.rb new file mode 100644 index 000000000..b9492d986 --- /dev/null +++ b/spec/unit/module_tool/installed_modules_spec.rb @@ -0,0 +1,49 @@ +require 'spec_helper' +require 'puppet/module_tool/installed_modules' +require 'puppet_spec/modules' + +describe Puppet::ModuleTool::InstalledModules do + include PuppetSpec::Files + + around do |example| + dir = tmpdir("deep_path") + + FileUtils.mkdir_p(@modpath = File.join(dir, "modpath")) + + @env = Puppet::Node::Environment.create(:env, [@modpath]) + Puppet.override(:current_environment => @env) do + example.run + end + end + + it 'works when given a semantic version' do + mod = PuppetSpec::Modules.create('goodsemver', @modpath, :metadata => {:version => '1.2.3'}) + installed = described_class.new(@env) + expect(installed.modules["puppetlabs-#{mod.name}"].version).to eq(Semantic::Version.parse('1.2.3')) + end + + it 'defaults when not given a semantic version' do + mod = PuppetSpec::Modules.create('badsemver', @modpath, :metadata => {:version => 'banana'}) + Puppet.expects(:warning).with(regexp_matches(/Semantic Version/)) + installed = described_class.new(@env) + expect(installed.modules["puppetlabs-#{mod.name}"].version).to eq(Semantic::Version.parse('0.0.0')) + end + + it 'defaults when not given a full semantic version' do + mod = PuppetSpec::Modules.create('badsemver', @modpath, :metadata => {:version => '1.2'}) + Puppet.expects(:warning).with(regexp_matches(/Semantic Version/)) + installed = described_class.new(@env) + expect(installed.modules["puppetlabs-#{mod.name}"].version).to eq(Semantic::Version.parse('0.0.0')) + end + + it 'still works if there is an invalid version in one of the modules' do + mod1 = PuppetSpec::Modules.create('badsemver', @modpath, :metadata => {:version => 'banana'}) + mod2 = PuppetSpec::Modules.create('goodsemver', @modpath, :metadata => {:version => '1.2.3'}) + mod3 = PuppetSpec::Modules.create('notquitesemver', @modpath, :metadata => {:version => '1.2'}) + Puppet.expects(:warning).with(regexp_matches(/Semantic Version/)).twice + installed = described_class.new(@env) + expect(installed.modules["puppetlabs-#{mod1.name}"].version).to eq(Semantic::Version.parse('0.0.0')) + expect(installed.modules["puppetlabs-#{mod2.name}"].version).to eq(Semantic::Version.parse('1.2.3')) + expect(installed.modules["puppetlabs-#{mod3.name}"].version).to eq(Semantic::Version.parse('0.0.0')) + end +end diff --git a/spec/unit/module_tool/metadata_spec.rb b/spec/unit/module_tool/metadata_spec.rb index 3b925c644..fce9c8f8d 100644 --- a/spec/unit/module_tool/metadata_spec.rb +++ b/spec/unit/module_tool/metadata_spec.rb @@ -5,6 +5,19 @@ describe Puppet::ModuleTool::Metadata do let(:data) { {} } let(:metadata) { Puppet::ModuleTool::Metadata.new } + describe 'property lookups' do + subject { metadata } + + %w[ name version author summary license source project_page issues_url + dependencies dashed_name release_name description ].each do |prop| + describe "##{prop}" do + it "responds to the property" do + subject.send(prop) + end + end + end + end + describe "#update" do subject { metadata.update(data) } @@ -156,6 +169,61 @@ describe Puppet::ModuleTool::Metadata do end + context "with a valid dependency" do + let(:data) { {'dependencies' => [{'name' => 'puppetlabs-goodmodule'}] }} + + it "adds the dependency" do + subject.dependencies.size.should == 1 + end + end + + context "with a invalid dependency name" do + let(:data) { {'dependencies' => [{'name' => 'puppetlabsbadmodule'}] }} + + it "raises an exception" do + expect { subject }.to raise_error(ArgumentError) + end + end + + context "with a valid dependency version range" do + let(:data) { {'dependencies' => [{'name' => 'puppetlabs-badmodule', 'version_requirement' => '>= 2.0.0'}] }} + + it "adds the dependency" do + subject.dependencies.size.should == 1 + end + end + + context "with a invalid version range" do + let(:data) { {'dependencies' => [{'name' => 'puppetlabsbadmodule', 'version_requirement' => '>= banana'}] }} + + it "raises an exception" do + expect { subject }.to raise_error(ArgumentError) + end + end + + context "with duplicate dependencies" do + let(:data) { {'dependencies' => [{'name' => 'puppetlabs-dupmodule', 'version_requirement' => '1.0.0'}, + {'name' => 'puppetlabs-dupmodule', 'version_requirement' => '0.0.1'}] } + } + + it "raises an exception" do + expect { subject }.to raise_error(ArgumentError) + end + end + + context "adding a duplicate dependency" do + let(:data) { {'dependencies' => [{'name' => 'puppetlabs-origmodule', 'version_requirement' => '1.0.0'}] }} + + it "with a different version raises an exception" do + metadata.add_dependency('puppetlabs-origmodule', '>= 0.0.1') + expect { subject }.to raise_error(ArgumentError) + end + + it "with the same version does not add another dependency" do + metadata.add_dependency('puppetlabs-origmodule', '1.0.0') + subject.dependencies.size.should == 1 + end + end end describe '#dashed_name' do @@ -202,8 +270,8 @@ describe Puppet::ModuleTool::Metadata do describe "#to_hash" do subject { metadata.to_hash } - its(:keys) do - subject.sort.should == %w[ name version author summary license source issues_url project_page dependencies ].sort + it "contains the default set of keys" do + subject.keys.sort.should == %w[ name version author summary license source issues_url project_page dependencies ].sort end describe "['license']" do @@ -213,8 +281,8 @@ describe Puppet::ModuleTool::Metadata do end describe "['dependencies']" do - it "defaults to an empty Array" do - subject['dependencies'].should == [] + it "defaults to an empty set" do + subject['dependencies'].should == Set.new end end diff --git a/spec/unit/module_tool/tar/mini_spec.rb b/spec/unit/module_tool/tar/mini_spec.rb index ef030611b..179952741 100644 --- a/spec/unit/module_tool/tar/mini_spec.rb +++ b/spec/unit/module_tool/tar/mini_spec.rb @@ -54,6 +54,7 @@ describe Puppet::ModuleTool::Tar::Mini, :if => (Puppet.features.minitar? and Pup reader = mock('GzipReader') Zlib::GzipReader.expects(:open).with(sourcefile).yields(reader) - Archive::Tar::Minitar.expects(:unpack).with(reader, destdir).yields(type, name, nil) + minitar.expects(:find_valid_files).with(reader).returns([name]) + Archive::Tar::Minitar.expects(:unpack).with(reader, destdir, [name]).yields(type, name, nil) end end diff --git a/spec/unit/network/authentication_spec.rb b/spec/unit/network/authentication_spec.rb index 8f3653cad..5e2f2de87 100755 --- a/spec/unit/network/authentication_spec.rb +++ b/spec/unit/network/authentication_spec.rb @@ -81,6 +81,10 @@ describe Puppet::Network::Authentication do cert.stubs(:unmunged_name).returns('foo') end + after(:all) do + reload_module + end + it "should log a warning if a certificate's expiration is near" do logger.expects(:warning) subject.warn_if_near_expiration(cert) diff --git a/spec/unit/network/http/api/v2/environments_spec.rb b/spec/unit/network/http/api/v2/environments_spec.rb index 6c6d7a581..993e55011 100644 --- a/spec/unit/network/http/api/v2/environments_spec.rb +++ b/spec/unit/network/http/api/v2/environments_spec.rb @@ -23,20 +23,41 @@ describe Puppet::Network::HTTP::API::V2::Environments do "production" => { "settings" => { "modulepath" => [File.expand_path("/first"), File.expand_path("/second")], - "manifest" => File.expand_path("/manifests") + "manifest" => File.expand_path("/manifests"), + "environment_timeout" => 0, + "config_version" => "" } } } }) end - it "the response conforms to the environments schema" do + it "the response conforms to the environments schema for unlimited timeout" do + conf_stub = stub 'conf_stub' + conf_stub.expects(:environment_timeout).returns(1.0 / 0.0) environment = Puppet::Node::Environment.create(:production, []) - handler = Puppet::Network::HTTP::API::V2::Environments.new(Puppet::Environments::Static.new(environment)) + env_loader = Puppet::Environments::Static.new(environment) + env_loader.expects(:get_conf).with(:production).returns(conf_stub) + handler = Puppet::Network::HTTP::API::V2::Environments.new(env_loader) response = Puppet::Network::HTTP::MemoryResponse.new handler.call(Puppet::Network::HTTP::Request.from_hash(:headers => { 'accept' => 'application/json' }), response) expect(response.body).to validate_against('api/schemas/environments.json') end + + it "the response conforms to the environments schema for integer timeout" do + conf_stub = stub 'conf_stub' + conf_stub.expects(:environment_timeout).returns(1) + environment = Puppet::Node::Environment.create(:production, []) + env_loader = Puppet::Environments::Static.new(environment) + env_loader.expects(:get_conf).with(:production).returns(conf_stub) + handler = Puppet::Network::HTTP::API::V2::Environments.new(env_loader) + response = Puppet::Network::HTTP::MemoryResponse.new + + handler.call(Puppet::Network::HTTP::Request.from_hash(:headers => { 'accept' => 'application/json' }), response) + + expect(response.body).to validate_against('api/schemas/environments.json') + end + end diff --git a/spec/unit/network/http/connection_spec.rb b/spec/unit/network/http/connection_spec.rb index a5e6f64ae..ef6ca65d6 100644..100755 --- a/spec/unit/network/http/connection_spec.rb +++ b/spec/unit/network/http/connection_spec.rb @@ -11,50 +11,30 @@ describe Puppet::Network::HTTP::Connection do let (:httpok) { Net::HTTPOK.new('1.1', 200, '') } context "when providing HTTP connections" do - after do - Puppet::Network::HTTP::Connection.instance_variable_set("@ssl_host", nil) - end - context "when initializing http instances" do - before :each do - # All of the cert stuff is tested elsewhere - Puppet::Network::HTTP::Connection.stubs(:cert_setup) - end - it "should return an http instance created with the passed host and port" do - http = subject.send(:connection) - http.should be_an_instance_of Net::HTTP - http.address.should == host - http.port.should == port + conn = Puppet::Network::HTTP::Connection.new(host, port, :verify => Puppet::SSL::Validator.no_validator) + + expect(conn.address).to eq(host) + expect(conn.port).to eq(port) end it "should enable ssl on the http instance by default" do - http = subject.send(:connection) - http.should be_use_ssl - end + conn = Puppet::Network::HTTP::Connection.new(host, port, :verify => Puppet::SSL::Validator.no_validator) - it "can set ssl using an option" do - 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 + expect(conn).to be_use_ssl end - context "proxy and timeout settings should propagate" do - 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 - Puppet[:configtimeout] = 120 - end + it "can disable ssl using an option" do + conn = Puppet::Network::HTTP::Connection.new(host, port, :use_ssl => false, :verify => Puppet::SSL::Validator.no_validator) - its(:open_timeout) { should == Puppet[:configtimeout] } - its(:read_timeout) { should == Puppet[:configtimeout] } - its(:proxy_address) { should == Puppet[:http_proxy_host] } - its(:proxy_port) { should == Puppet[:http_proxy_port] } + expect(conn).to_not be_use_ssl end - it "should not set a proxy if the value is 'none'" do - Puppet[:http_proxy_host] = 'none' - subject.send(:connection).proxy_address.should be_nil + it "can enable ssl using an option" do + conn = Puppet::Network::HTTP::Connection.new(host, port, :use_ssl => true, :verify => Puppet::SSL::Validator.no_validator) + + expect(conn).to be_use_ssl end it "should raise Puppet::Error when invalid options are specified" do @@ -96,7 +76,44 @@ describe Puppet::Network::HTTP::Connection do end end - context "when validating HTTPS requests" do + class ConstantErrorValidator + def initialize(args) + @fails_with = args[:fails_with] + @error_string = args[:error_string] || "" + @peer_certs = args[:peer_certs] || [] + end + + def setup_connection(connection) + connection.stubs(:start).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 + + shared_examples_for 'ssl verifier' do include PuppetSpec::Files let (:host) { "my_server" } @@ -117,11 +134,11 @@ describe Puppet::Network::HTTP::Connection do 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')])) + 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') @@ -150,86 +167,104 @@ describe Puppet::Network::HTTP::Connection do host, port, :verify => NoProblemsValidator.new(cert)) + Net::HTTP.any_instance.stubs(:start) Net::HTTP.any_instance.stubs(:request).returns(httpok) connection.expects(:warn_if_near_expiration).with(cert) connection.get('request') end + end - class ConstantErrorValidator - def initialize(args) - @fails_with = args[:fails_with] - @error_string = args[:error_string] || "" - @peer_certs = args[:peer_certs] || [] - end + context "when using single use HTTPS connections" do + it_behaves_like 'ssl verifier' do + end + end - def setup_connection(connection) - connection.stubs(:request).with do - true - end.raises(OpenSSL::SSL::SSLError.new(@fails_with)) + context "when using persistent HTTPS connections" do + around :each do |example| + pool = Puppet::Network::HTTP::Pool.new + Puppet.override(:http_pool => pool) do + example.run end + pool.close + end - def peer_certs - @peer_certs - end + it_behaves_like 'ssl verifier' do + end + end - def verify_errors - [@error_string] - end + context "when response is a redirect" do + let (:site) { Puppet::Network::HTTP::Site.new('http', 'my_server', 8140) } + let (:other_site) { Puppet::Network::HTTP::Site.new('http', 'redirected', 9292) } + let (:other_path) { "other-path" } + let (:verify) { Puppet::SSL::Validator.no_validator } + let (:subject) { Puppet::Network::HTTP::Connection.new(site.host, site.port, :use_ssl => false, :verify => verify) } + let (:httpredirection) do + response = Net::HTTPFound.new('1.1', 302, 'Moved Temporarily') + response['location'] = "#{other_site.addr}/#{other_path}" + response.stubs(:read_body).returns("This resource has moved") + response end - class NoProblemsValidator - def initialize(cert) - @cert = cert - end + def create_connection(site, options) + options[:use_ssl] = site.use_ssl? + Puppet::Network::HTTP::Connection.new(site.host, site.port, options) + end - def setup_connection(connection) - end + it "should redirect to the final resource location" do + http = stub('http') + http.stubs(:request).returns(httpredirection).then.returns(httpok) - def peer_certs - [@cert] - end + seq = sequence('redirection') + pool = Puppet.lookup(:http_pool) + pool.expects(:with_connection).with(site, anything).yields(http).in_sequence(seq) + pool.expects(:with_connection).with(other_site, anything).yields(http).in_sequence(seq) - def verify_errors - [] - end + conn = create_connection(site, :verify => verify) + conn.get('/foo') end - end - context "when response is a redirect" 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, :verify => Puppet::SSL::Validator.no_validator) } - let (:httpredirection) { Net::HTTPFound.new('1.1', 302, 'Moved Temporarily') } + def expects_redirection(conn, &block) + http = stub('http') + http.stubs(:request).returns(httpredirection) - before :each do - httpredirection['location'] = "http://#{other_host}:#{other_port}/#{other_path}" - httpredirection.stubs(:read_body).returns("This resource has moved") + pool = Puppet.lookup(:http_pool) + pool.expects(:with_connection).with(site, anything).yields(http) + pool + end - socket = stub_everything("socket") - TCPSocket.stubs(:open).returns(socket) + def expects_limit_exceeded(conn) + expect { + conn.get('/') + }.to raise_error(Puppet::Network::HTTP::RedirectionLimitExceededException) + end - Net::HTTP::Get.any_instance.stubs(:exec).returns("") - Net::HTTP::Post.any_instance.stubs(:exec).returns("") + it "should not redirect when the limit is 0" do + conn = create_connection(site, :verify => verify, :redirect_limit => 0) + + pool = expects_redirection(conn) + pool.expects(:with_connection).with(other_site, anything).never + + expects_limit_exceeded(conn) end - it "should redirect to the final resource location" do - httpok.stubs(:read_body).returns(:body) - Net::HTTPResponse.stubs(:read_new).returns(httpredirection).then.returns(httpok) + it "should redirect only once" do + conn = create_connection(site, :verify => verify, :redirect_limit => 1) + + pool = expects_redirection(conn) + pool.expects(:with_connection).with(other_site, anything).once - subject.get("/foo").body.should == :body - subject.port.should == other_port - subject.address.should == other_host + expects_limit_exceeded(conn) end - it "should raise an error after too many redirections" do - Net::HTTPResponse.stubs(:read_new).returns(httpredirection) + it "should raise an exception when the redirect limit is exceeded" do + conn = create_connection(site, :verify => verify, :redirect_limit => 3) - expect { - subject.get("/foo") - }.to raise_error(Puppet::Network::HTTP::RedirectionLimitExceededException) + pool = expects_redirection(conn) + pool.expects(:with_connection).with(other_site, anything).times(3) + + expects_limit_exceeded(conn) end end diff --git a/spec/unit/network/http/factory_spec.rb b/spec/unit/network/http/factory_spec.rb new file mode 100755 index 000000000..107ededcd --- /dev/null +++ b/spec/unit/network/http/factory_spec.rb @@ -0,0 +1,82 @@ +#! /usr/bin/env ruby +require 'spec_helper' +require 'puppet/network/http' + +describe Puppet::Network::HTTP::Factory do + before :each do + Puppet::SSL::Key.indirection.terminus_class = :memory + Puppet::SSL::CertificateRequest.indirection.terminus_class = :memory + end + + let(:site) { Puppet::Network::HTTP::Site.new('https', 'www.example.com', 443) } + def create_connection(site) + factory = Puppet::Network::HTTP::Factory.new + + factory.create_connection(site) + end + + it 'creates a connection for the site' do + conn = create_connection(site) + + expect(conn.use_ssl?).to be_true + expect(conn.address).to eq(site.host) + expect(conn.port).to eq(site.port) + end + + it 'creates a connection that has not yet been started' do + conn = create_connection(site) + + expect(conn).to_not be_started + end + + it 'creates a connection supporting at least HTTP 1.1' do + conn = create_connection(site) + + expect(any_of(conn.class.version_1_1?, conn.class.version_1_1?)).to be_true + end + + context "proxy settings" do + let(:proxy_host) { 'myhost' } + let(:proxy_port) { 432 } + + it "should not set a proxy if the value is 'none'" do + Puppet[:http_proxy_host] = 'none' + conn = create_connection(site) + + expect(conn.proxy_address).to be_nil + end + + it 'sets proxy_address' do + Puppet[:http_proxy_host] = proxy_host + conn = create_connection(site) + + expect(conn.proxy_address).to eq(proxy_host) + end + + it 'sets proxy address and port' do + Puppet[:http_proxy_host] = proxy_host + Puppet[:http_proxy_port] = proxy_port + conn = create_connection(site) + + expect(conn.proxy_port).to eq(proxy_port) + end + + context 'socket timeouts' do + let(:timeout) { 5 } + + it 'sets open timeout' do + Puppet[:configtimeout] = timeout + conn = create_connection(site) + + expect(conn.open_timeout).to eq(timeout) + end + + it 'sets read timeout' do + Puppet[:configtimeout] = timeout + conn = create_connection(site) + + expect(conn.read_timeout).to eq(timeout) + end + end + end +end diff --git a/spec/unit/network/http/handler_spec.rb b/spec/unit/network/http/handler_spec.rb index 345818b4a..25df9d270 100755 --- a/spec/unit/network/http/handler_spec.rb +++ b/spec/unit/network/http/handler_spec.rb @@ -103,31 +103,30 @@ describe Puppet::Network::HTTP::Handler do handler.stubs(:warn_if_near_expiration) end - it "should check the client certificate for upcoming expiration" do - request = a_request - cert = mock 'cert' - handler.expects(:client_cert).returns(cert).with(request) - handler.expects(:warn_if_near_expiration).with(cert) - - handler.process(request, response) - end - it "should setup a profiler when the puppet-profiling header exists" do request = a_request request[:headers][Puppet::Network::HTTP::HEADER_ENABLE_PROFILING.downcase] = "true" - handler.process(request, response) + p = HandlerTestProfiler.new + + Puppet::Util::Profiler.expects(:add_profiler).with { |profiler| + profiler.is_a? Puppet::Util::Profiler::WallClock + }.returns(p) - Puppet::Util::Profiler.current.should be_kind_of(Puppet::Util::Profiler::WallClock) + Puppet::Util::Profiler.expects(:remove_profiler).with { |profiler| + profiler == p + } + + handler.process(request, response) end it "should not setup profiler when the profile parameter is missing" do request = a_request request[:params] = { } - handler.process(request, response) + Puppet::Util::Profiler.expects(:add_profiler).never - Puppet::Util::Profiler.current.should == Puppet::Util::Profiler::NONE + handler.process(request, response) end it "should raise an error if the request is formatted in an unknown format" do @@ -219,4 +218,15 @@ describe Puppet::Network::HTTP::Handler do request[:headers] || {} end end + + class HandlerTestProfiler + def start(metric, description) + end + + def finish(context, metric, description) + end + + def shutdown() + end + end end diff --git a/spec/unit/network/http/nocache_pool_spec.rb b/spec/unit/network/http/nocache_pool_spec.rb new file mode 100755 index 000000000..69e2d2e9a --- /dev/null +++ b/spec/unit/network/http/nocache_pool_spec.rb @@ -0,0 +1,43 @@ +#! /usr/bin/env ruby +require 'spec_helper' + +require 'puppet/network/http' +require 'puppet/network/http/connection' + +describe Puppet::Network::HTTP::NoCachePool do + let(:site) { Puppet::Network::HTTP::Site.new('https', 'rubygems.org', 443) } + let(:verify) { stub('verify', :setup_connection => nil) } + + it 'yields a connection' do + http = stub('http') + + factory = Puppet::Network::HTTP::Factory.new + factory.stubs(:create_connection).returns(http) + pool = Puppet::Network::HTTP::NoCachePool.new(factory) + + expect { |b| + pool.with_connection(site, verify, &b) + }.to yield_with_args(http) + end + + it 'yields a new connection each time' do + http1 = stub('http1') + http2 = stub('http2') + + factory = Puppet::Network::HTTP::Factory.new + factory.stubs(:create_connection).returns(http1).then.returns(http2) + pool = Puppet::Network::HTTP::NoCachePool.new(factory) + + expect { |b| + pool.with_connection(site, verify, &b) + }.to yield_with_args(http1) + + expect { |b| + pool.with_connection(site, verify, &b) + }.to yield_with_args(http2) + end + + it 'has a close method' do + Puppet::Network::HTTP::NoCachePool.new.close + end +end diff --git a/spec/unit/network/http/pool_spec.rb b/spec/unit/network/http/pool_spec.rb new file mode 100755 index 000000000..aef100953 --- /dev/null +++ b/spec/unit/network/http/pool_spec.rb @@ -0,0 +1,269 @@ +#! /usr/bin/env ruby +require 'spec_helper' + +require 'openssl' +require 'puppet/network/http' +require 'puppet/network/http_pool' + +describe Puppet::Network::HTTP::Pool do + before :each do + Puppet::SSL::Key.indirection.terminus_class = :memory + Puppet::SSL::CertificateRequest.indirection.terminus_class = :memory + end + + let(:site) do + Puppet::Network::HTTP::Site.new('https', 'rubygems.org', 443) + end + + let(:different_site) do + Puppet::Network::HTTP::Site.new('https', 'github.com', 443) + end + + let(:verify) do + stub('verify', :setup_connection => nil) + end + + def create_pool + Puppet::Network::HTTP::Pool.new + end + + def create_pool_with_connections(site, *connections) + pool = Puppet::Network::HTTP::Pool.new + connections.each do |conn| + pool.release(site, conn) + end + pool + end + + def create_pool_with_expired_connections(site, *connections) + # setting keepalive timeout to -1 ensures any newly added + # connections have already expired + pool = Puppet::Network::HTTP::Pool.new(-1) + connections.each do |conn| + pool.release(site, conn) + end + pool + end + + def create_connection(site) + stub(site.addr, :started? => false, :start => nil, :finish => nil, :use_ssl? => true, :verify_mode => OpenSSL::SSL::VERIFY_PEER) + end + + context 'when yielding a connection' do + it 'yields a connection' do + conn = create_connection(site) + pool = create_pool_with_connections(site, conn) + + expect { |b| + pool.with_connection(site, verify, &b) + }.to yield_with_args(conn) + end + + it 'returns the connection to the pool' do + conn = create_connection(site) + pool = create_pool + pool.release(site, conn) + + pool.with_connection(site, verify) { |c| } + + expect(pool.pool[site].first.connection).to eq(conn) + end + + it 'can yield multiple connections to the same site' do + lru_conn = create_connection(site) + mru_conn = create_connection(site) + pool = create_pool_with_connections(site, lru_conn, mru_conn) + + pool.with_connection(site, verify) do |a| + expect(a).to eq(mru_conn) + + pool.with_connection(site, verify) do |b| + expect(b).to eq(lru_conn) + end + end + end + + it 'propagates exceptions' do + conn = create_connection(site) + pool = create_pool + pool.release(site, conn) + + expect { + pool.with_connection(site, verify) do |c| + raise IOError, 'connection reset' + end + }.to raise_error(IOError, 'connection reset') + end + + it 'does not re-cache connections when an error occurs' do + # we're not distinguishing between network errors that would + # suggest we close the socket, and other errors + conn = create_connection(site) + pool = create_pool + pool.release(site, conn) + + pool.expects(:release).with(site, conn).never + + pool.with_connection(site, verify) do |c| + raise IOError, 'connection reset' + end rescue nil + end + + context 'when releasing connections' do + it 'releases HTTP connections' do + conn = create_connection(site) + conn.expects(:use_ssl?).returns(false) + + pool = create_pool_with_connections(site, conn) + pool.expects(:release).with(site, conn) + + pool.with_connection(site, verify) {|c| } + end + + it 'releases secure HTTPS connections' do + conn = create_connection(site) + conn.expects(:use_ssl?).returns(true) + conn.expects(:verify_mode).returns(OpenSSL::SSL::VERIFY_PEER) + + pool = create_pool_with_connections(site, conn) + pool.expects(:release).with(site, conn) + + pool.with_connection(site, verify) {|c| } + end + + it 'closes insecure HTTPS connections' do + conn = create_connection(site) + conn.expects(:use_ssl?).returns(true) + conn.expects(:verify_mode).returns(OpenSSL::SSL::VERIFY_NONE) + + pool = create_pool_with_connections(site, conn) + + pool.expects(:release).with(site, conn).never + + pool.with_connection(site, verify) {|c| } + end + end + end + + context 'when borrowing' do + it 'returns a new connection if the pool is empty' do + conn = create_connection(site) + pool = create_pool + pool.factory.expects(:create_connection).with(site).returns(conn) + + expect(pool.borrow(site, verify)).to eq(conn) + end + + it 'returns a matching connection' do + conn = create_connection(site) + pool = create_pool_with_connections(site, conn) + + pool.factory.expects(:create_connection).never + + expect(pool.borrow(site, verify)).to eq(conn) + end + + it 'returns a new connection if there are no matching sites' do + different_conn = create_connection(different_site) + pool = create_pool_with_connections(different_site, different_conn) + + conn = create_connection(site) + pool.factory.expects(:create_connection).with(site).returns(conn) + + expect(pool.borrow(site, verify)).to eq(conn) + end + + it 'returns started connections' do + conn = create_connection(site) + conn.expects(:start) + + pool = create_pool + pool.factory.expects(:create_connection).with(site).returns(conn) + + expect(pool.borrow(site, verify)).to eq(conn) + end + + it "doesn't start a cached connection" do + conn = create_connection(site) + conn.expects(:start).never + + pool = create_pool_with_connections(site, conn) + pool.borrow(site, verify) + end + + it 'returns the most recently used connection from the pool' do + least_recently_used = create_connection(site) + most_recently_used = create_connection(site) + + pool = create_pool_with_connections(site, least_recently_used, most_recently_used) + expect(pool.borrow(site, verify)).to eq(most_recently_used) + end + + it 'finishes expired connections' do + conn = create_connection(site) + conn.expects(:finish) + + pool = create_pool_with_expired_connections(site, conn) + pool.factory.expects(:create_connection => stub('conn', :start => nil)) + + pool.borrow(site, verify) + end + + it 'logs an exception if it fails to close an expired connection' do + Puppet.expects(:log_exception).with(is_a(IOError), "Failed to close connection for #{site}: read timeout") + + conn = create_connection(site) + conn.expects(:finish).raises(IOError, 'read timeout') + + pool = create_pool_with_expired_connections(site, conn) + pool.factory.expects(:create_connection => stub('open_conn', :start => nil)) + + pool.borrow(site, verify) + end + end + + context 'when releasing a connection' do + it 'adds the connection to an empty pool' do + conn = create_connection(site) + + pool = create_pool + pool.release(site, conn) + + expect(pool.pool[site].first.connection).to eq(conn) + end + + it 'adds the connection to a pool with a connection for the same site' do + pool = create_pool + pool.release(site, create_connection(site)) + pool.release(site, create_connection(site)) + + expect(pool.pool[site].count).to eq(2) + end + + it 'adds the connection to a pool with a connection for a different site' do + pool = create_pool + pool.release(site, create_connection(site)) + pool.release(different_site, create_connection(different_site)) + + expect(pool.pool[site].count).to eq(1) + expect(pool.pool[different_site].count).to eq(1) + end + end + + context 'when closing' do + it 'clears the pool' do + pool = create_pool + pool.close + + expect(pool.pool).to be_empty + end + + it 'closes all cached connections' do + conn = create_connection(site) + conn.expects(:finish) + + pool = create_pool_with_connections(site, conn) + pool.close + end + end +end diff --git a/spec/unit/network/http/rack/rest_spec.rb b/spec/unit/network/http/rack/rest_spec.rb index 165b6ceb9..c35b789a2 100755 --- a/spec/unit/network/http/rack/rest_spec.rb +++ b/spec/unit/network/http/rack/rest_spec.rb @@ -12,11 +12,11 @@ describe "Puppet::Network::HTTP::RackREST", :if => Puppet.features.rack? do before :all do @model_class = stub('indirected model class') Puppet::Indirector::Indirection.stubs(:model).with(:foo).returns(@model_class) - @handler = Puppet::Network::HTTP::RackREST.new(:handler => :foo) end before :each do @response = Rack::Response.new + @handler = Puppet::Network::HTTP::RackREST.new(:handler => :foo) end def mk_req(uri, opts = {}) diff --git a/spec/unit/network/http/session_spec.rb b/spec/unit/network/http/session_spec.rb new file mode 100755 index 000000000..4eba67d7d --- /dev/null +++ b/spec/unit/network/http/session_spec.rb @@ -0,0 +1,43 @@ +#! /usr/bin/env ruby +require 'spec_helper' + +require 'puppet/network/http' + +describe Puppet::Network::HTTP::Session do + let(:connection) { stub('connection') } + + def create_session(connection, expiration_time = nil) + expiration_time ||= Time.now + 60 * 60 + + Puppet::Network::HTTP::Session.new(connection, expiration_time) + end + + it 'provides access to its connection' do + session = create_session(connection) + + session.connection.should == connection + end + + it 'expires a connection whose expiration time is in the past' do + now = Time.now + past = now - 1 + + session = create_session(connection, past) + session.expired?(now).should be_true + end + + it 'expires a connection whose expiration time is now' do + now = Time.now + + session = create_session(connection, now) + session.expired?(now).should be_true + end + + it 'does not expire a connection whose expiration time is in the future' do + now = Time.now + future = now + 1 + + session = create_session(connection, future) + session.expired?(now).should be_false + end +end diff --git a/spec/unit/network/http/site_spec.rb b/spec/unit/network/http/site_spec.rb new file mode 100755 index 000000000..06fcbf83d --- /dev/null +++ b/spec/unit/network/http/site_spec.rb @@ -0,0 +1,90 @@ +#! /usr/bin/env ruby +require 'spec_helper' + +require 'puppet/network/http' + +describe Puppet::Network::HTTP::Site do + let(:scheme) { 'https' } + let(:host) { 'rubygems.org' } + let(:port) { 443 } + + def create_site(scheme, host, port) + Puppet::Network::HTTP::Site.new(scheme, host, port) + end + + it 'accepts scheme, host, and port' do + site = create_site(scheme, host, port) + + expect(site.scheme).to eq(scheme) + expect(site.host).to eq(host) + expect(site.port).to eq(port) + end + + it 'generates an external URI string' do + site = create_site(scheme, host, port) + + expect(site.addr).to eq("https://rubygems.org:443") + end + + it 'considers sites to be different when the scheme is different' do + https_site = create_site('https', host, port) + http_site = create_site('http', host, port) + + expect(https_site).to_not eq(http_site) + end + + it 'considers sites to be different when the host is different' do + rubygems_site = create_site(scheme, 'rubygems.org', port) + github_site = create_site(scheme, 'github.com', port) + + expect(rubygems_site).to_not eq(github_site) + end + + it 'considers sites to be different when the port is different' do + site_443 = create_site(scheme, host, 443) + site_80 = create_site(scheme, host, 80) + + expect(site_443).to_not eq(site_80) + end + + it 'compares values when determining equality' do + site = create_site(scheme, host, port) + + sites = {} + sites[site] = site + + another_site = create_site(scheme, host, port) + + expect(sites.include?(another_site)).to be_true + end + + it 'computes the same hash code for equivalent objects' do + site = create_site(scheme, host, port) + same_site = create_site(scheme, host, port) + + expect(site.hash).to eq(same_site.hash) + end + + it 'uses ssl with https' do + site = create_site('https', host, port) + + expect(site).to be_use_ssl + end + + it 'does not use ssl with http' do + site = create_site('http', host, port) + + expect(site).to_not be_use_ssl + end + + it 'moves to a new URI location' do + site = create_site('http', 'host1', 80) + + uri = URI.parse('https://host2:443/some/where/else') + new_site = site.move_to(uri) + + expect(new_site.scheme).to eq('https') + expect(new_site.host).to eq('host2') + expect(new_site.port).to eq(443) + end +end diff --git a/spec/unit/network/http/webrick_spec.rb b/spec/unit/network/http/webrick_spec.rb index 17f61e339..edeb439a9 100755 --- a/spec/unit/network/http/webrick_spec.rb +++ b/spec/unit/network/http/webrick_spec.rb @@ -127,7 +127,7 @@ describe Puppet::Network::HTTP::WEBrick do server.setup_logger end - it "should use the masterlog if the run_mode is master" do + it "should use the masterhttplog if the run_mode is master" do Puppet.run_mode.stubs(:master?).returns(true) log = make_absolute("/master/log") Puppet[:masterhttplog] = log diff --git a/spec/unit/network/http_pool_spec.rb b/spec/unit/network/http_pool_spec.rb index d8b84232e..a9c5783f2 100755 --- a/spec/unit/network/http_pool_spec.rb +++ b/spec/unit/network/http_pool_spec.rb @@ -9,7 +9,6 @@ describe Puppet::Network::HttpPool do end describe "when managing http instances" do - it "should return an http instance created with the passed host and port" do http = Puppet::Network::HttpPool.http_instance("me", 54321) http.should be_an_instance_of Puppet::Network::HTTP::Connection @@ -47,7 +46,6 @@ describe Puppet::Network::HttpPool do Puppet::Network::HttpPool.http_instance("me", 54321, true).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') @@ -77,12 +75,18 @@ describe Puppet::Network::HttpPool do setup_standard_ssl_host end - it 'can enable peer verification' do - Puppet::Network::HttpPool.http_instance("me", 54321, true, true).send(:connection).verify_mode.should == OpenSSL::SSL::VERIFY_PEER + it 'enables peer verification by default' do + response = Net::HTTPOK.new('1.1', 200, 'body') + conn = Puppet::Network::HttpPool.http_instance("me", 54321, true) + conn.expects(:execute_request).with { |http, request| expect(http.verify_mode).to eq(OpenSSL::SSL::VERIFY_PEER) }.returns(response) + conn.get('/') end it 'can disable peer verification' do - Puppet::Network::HttpPool.http_instance("me", 54321, true, false).send(:connection).verify_mode.should == OpenSSL::SSL::VERIFY_NONE + response = Net::HTTPOK.new('1.1', 200, 'body') + conn = Puppet::Network::HttpPool.http_instance("me", 54321, true, false) + conn.expects(:execute_request).with { |http, request| expect(http.verify_mode).to eq(OpenSSL::SSL::VERIFY_NONE) }.returns(response) + conn.get('/') end end @@ -91,5 +95,4 @@ describe Puppet::Network::HttpPool do should_not equal(Puppet::Network::HttpPool.http_instance("me", 54321)) end end - end diff --git a/spec/unit/network/http_spec.rb b/spec/unit/network/http_spec.rb new file mode 100755 index 000000000..4a149d3a8 --- /dev/null +++ b/spec/unit/network/http_spec.rb @@ -0,0 +1,10 @@ +#! /usr/bin/env ruby +require 'spec_helper' +require 'puppet/network/http' + +describe Puppet::Network::HTTP do + it 'defines an http_pool context' do + pool = Puppet.lookup(:http_pool) + expect(pool).to be_a(Puppet::Network::HTTP::NoCachePool) + end +end diff --git a/spec/unit/node/environment_spec.rb b/spec/unit/node/environment_spec.rb index 0881b8576..3df5d329c 100755 --- a/spec/unit/node/environment_spec.rb +++ b/spec/unit/node/environment_spec.rb @@ -43,6 +43,42 @@ describe Puppet::Node::Environment do Puppet::Node::Environment.new(one).should equal(one) end + describe "equality" do + it "works as a hash key" do + base = Puppet::Node::Environment.create(:first, ["modules"], "manifests") + same = Puppet::Node::Environment.create(:first, ["modules"], "manifests") + different = Puppet::Node::Environment.create(:first, ["different"], "manifests") + hash = {} + + hash[base] = "base env" + hash[same] = "same env" + hash[different] = "different env" + + expect(hash[base]).to eq("same env") + expect(hash[different]).to eq("different env") + expect(hash).to have(2).item + end + + it "is equal when name, modules, and manifests are the same" do + base = Puppet::Node::Environment.create(:base, ["modules"], "manifests") + different_name = Puppet::Node::Environment.create(:different, base.full_modulepath, base.manifest) + + expect(base).to_not eq("not an environment") + + expect(base).to eq(base) + expect(base.hash).to eq(base.hash) + + expect(base.override_with(:modulepath => ["different"])).to_not eq(base) + expect(base.override_with(:modulepath => ["different"]).hash).to_not eq(base.hash) + + expect(base.override_with(:manifest => "different")).to_not eq(base) + expect(base.override_with(:manifest => "different").hash).to_not eq(base.hash) + + expect(different_name).to_not eq(base) + expect(different_name.hash).to_not eq(base.hash) + end + end + describe "overriding an existing environment" do let(:original_path) { [tmpdir('original')] } let(:new_path) { [tmpdir('new')] } @@ -151,6 +187,60 @@ describe Puppet::Node::Environment do end end + it "does not register conflicting_manifest_settings? when not using directory environments" do + expect(Puppet::Node::Environment.create(:directory, [], '/some/non/default/manifest.pp').conflicting_manifest_settings?).to be_false + end + + describe "when operating in the context of directory environments" do + before(:each) do + Puppet[:environmentpath] = "$confdir/environments" + Puppet[:default_manifest] = "/default/manifests/site.pp" + end + + it "has no conflicting_manifest_settings? when disable_per_environment_manifest is false" do + expect(Puppet::Node::Environment.create(:directory, [], '/some/non/default/manifest.pp').conflicting_manifest_settings?).to be_false + end + + context "when disable_per_environment_manifest is true" do + let(:config) { mock('config') } + let(:global_modulepath) { ["/global/modulepath"] } + let(:envconf) { Puppet::Settings::EnvironmentConf.new("/some/direnv", config, global_modulepath) } + + before(:each) do + Puppet[:disable_per_environment_manifest] = true + end + + def assert_manifest_conflict(expectation, envconf_manifest_value) + config.expects(:setting).with(:manifest).returns( + mock('setting', :value => envconf_manifest_value) + ) + environment = Puppet::Node::Environment.create(:directory, [], '/default/manifests/site.pp') + loader = Puppet::Environments::Static.new(environment) + loader.stubs(:get_conf).returns(envconf) + + Puppet.override(:environments => loader) do + expect(environment.conflicting_manifest_settings?).to eq(expectation) + end + end + + it "has conflicting_manifest_settings when environment.conf manifest was set" do + assert_manifest_conflict(true, '/some/envconf/manifest/site.pp') + end + + it "does not have conflicting_manifest_settings when environment.conf manifest is empty" do + assert_manifest_conflict(false, '') + end + + it "does not have conflicting_manifest_settings when environment.conf manifest is nil" do + assert_manifest_conflict(false, nil) + end + + it "does not have conflicting_manifest_settings when environment.conf manifest is an exact, uninterpolated match of default_manifest" do + assert_manifest_conflict(false, '/default/manifests/site.pp') + end + end + end + describe "when modeling a specific environment" do it "should have a method for returning the environment name" do Puppet::Node::Environment.new("testing").name.should == :testing diff --git a/spec/unit/node_spec.rb b/spec/unit/node_spec.rb index 85ec6e127..2de2b8279 100755 --- a/spec/unit/node_spec.rb +++ b/spec/unit/node_spec.rb @@ -157,11 +157,9 @@ describe Puppet::Node do Puppet::Node.should read_json_attribute('parameters').from(@node.to_pson).as({"a" => "b", "c" => "d"}) end - it "should include the environment" do - Puppet.override(:environments => env_loader) do - @node.environment = environment - Puppet::Node.should read_json_attribute('environment').from(@node.to_pson).as(environment) - end + it "deserializes environment to environment_name as a string" do + @node.environment = environment + Puppet::Node.should read_json_attribute('environment_name').from(@node.to_pson).as('bar') end end end diff --git a/spec/unit/parser/compiler_spec.rb b/spec/unit/parser/compiler_spec.rb index d1e855a20..0ca01f521 100755 --- a/spec/unit/parser/compiler_spec.rb +++ b/spec/unit/parser/compiler_spec.rb @@ -97,6 +97,13 @@ describe Puppet::Parser::Compiler do @compiler.environment.should equal(@node.environment) end + it "fails if the node's environment has conflicting manifest settings" do + conflicted_environment = Puppet::Node::Environment.create(:testing, [], '/some/environment.conf/manifest.pp') + conflicted_environment.stubs(:conflicting_manifest_settings?).returns(true) + @node.environment = conflicted_environment + expect { Puppet::Parser::Compiler.compile(@node) }.to raise_error(Puppet::Error, /disable_per_environment_manifest.*true.*environment.conf.*manifest.*conflict/) + end + it "should include the resource type collection helper" do Puppet::Parser::Compiler.ancestors.should be_include(Puppet::Resource::TypeCollectionHelper) end @@ -681,7 +688,7 @@ describe Puppet::Parser::Compiler do it "should skip classes that have already been evaluated" do @compiler.catalog.stubs(:tag) - @scope.stubs(:class_scope).with(@class).returns("something") + @scope.stubs(:class_scope).with(@class).returns(@scope) @compiler.expects(:add_resource).never @@ -694,7 +701,7 @@ describe Puppet::Parser::Compiler do it "should skip classes previously evaluated with different capitalization" do @compiler.catalog.stubs(:tag) @scope.stubs(:find_hostclass).with("MyClass",{:assume_fqname => false}).returns(@class) - @scope.stubs(:class_scope).with(@class).returns("something") + @scope.stubs(:class_scope).with(@class).returns(@scope) @compiler.expects(:add_resource).never @resource.expects(:evaluate).never Puppet::Parser::Resource.expects(:new).never diff --git a/spec/unit/parser/eparser_adapter_spec.rb b/spec/unit/parser/eparser_adapter_spec.rb deleted file mode 100644 index 173cfb783..000000000 --- a/spec/unit/parser/eparser_adapter_spec.rb +++ /dev/null @@ -1,407 +0,0 @@ -#! /usr/bin/env ruby -require 'spec_helper' -require 'puppet/parser/e_parser_adapter' - -describe Puppet::Parser do - - Puppet::Parser::AST - - before :each do - @known_resource_types = Puppet::Resource::TypeCollection.new("development") - @classic_parser = Puppet::Parser::Parser.new "development" - @parser = Puppet::Parser::EParserAdapter.new(@classic_parser) - @classic_parser.stubs(:known_resource_types).returns @known_resource_types - @true_ast = Puppet::Parser::AST::Boolean.new :value => true - end - - it "should require an environment at initialization" do - expect { - Puppet::Parser::EParserAdapter.new - }.to raise_error(ArgumentError, /wrong number of arguments/) - end - - describe "when parsing append operator" do - - it "should not raise syntax errors" do - expect { @parser.parse("$var += something") }.to_not raise_error - end - - it "should raise syntax error on incomplete syntax " do - expect { - @parser.parse("$var += ") - }.to raise_error(Puppet::ParseError, /Syntax error at end of file/) - end - - it "should create ast::VarDef with append=true" do - vardef = @parser.parse("$var += 2").code[0] - vardef.should be_a(Puppet::Parser::AST::VarDef) - vardef.append.should == true - end - - it "should work with arrays too" do - vardef = @parser.parse("$var += ['test']").code[0] - vardef.should be_a(Puppet::Parser::AST::VarDef) - vardef.append.should == true - end - - end - - describe "when parsing selector" do - it "should support hash access on the left hand side" do - expect { @parser.parse("$h = { 'a' => 'b' } $a = $h['a'] ? { 'b' => 'd', default => undef }") }.to_not raise_error - end - end - - describe "parsing 'unless'" do - it "should create the correct ast objects" do - Puppet::Parser::AST::Not.expects(:new).with { |h| h[:value].is_a?(Puppet::Parser::AST::Boolean) } - @parser.parse("unless false { $var = 1 }") - end - - it "should not raise an error with empty statements" do - expect { @parser.parse("unless false { }") }.to_not raise_error - end - - #test for bug #13296 - it "should not override 'unless' as a parameter inside resources" do - lambda { @parser.parse("exec {'/bin/echo foo': unless => '/usr/bin/false',}") }.should_not raise_error - end - end - - describe "when parsing parameter names" do - Puppet::Parser::Lexer::KEYWORDS.sort_tokens.each do |keyword| - it "should allow #{keyword} as a keyword" do - lambda { @parser.parse("exec {'/bin/echo foo': #{keyword} => '/usr/bin/false',}") }.should_not raise_error - end - end - end - - describe "when parsing 'if'" do - it "not, it should create the correct ast objects" do - Puppet::Parser::AST::Not.expects(:new).with { |h| h[:value].is_a?(Puppet::Parser::AST::Boolean) } - @parser.parse("if ! true { $var = 1 }") - end - - it "boolean operation, it should create the correct ast objects" do - Puppet::Parser::AST::BooleanOperator.expects(:new).with { - |h| h[:rval].is_a?(Puppet::Parser::AST::Boolean) and h[:lval].is_a?(Puppet::Parser::AST::Boolean) and h[:operator]=="or" - } - @parser.parse("if true or true { $var = 1 }") - - end - - it "comparison operation, it should create the correct ast objects" do - Puppet::Parser::AST::ComparisonOperator.expects(:new).with { - |h| h[:lval].is_a?(Puppet::Parser::AST::Name) and h[:rval].is_a?(Puppet::Parser::AST::Name) and h[:operator]=="<" - } - @parser.parse("if 1 < 2 { $var = 1 }") - - end - - end - - describe "when parsing if complex expressions" do - it "should create a correct ast tree" do - aststub = stub_everything 'ast' - Puppet::Parser::AST::ComparisonOperator.expects(:new).with { - |h| h[:rval].is_a?(Puppet::Parser::AST::Name) and h[:lval].is_a?(Puppet::Parser::AST::Name) and h[:operator]==">" - }.returns(aststub) - Puppet::Parser::AST::ComparisonOperator.expects(:new).with { - |h| h[:rval].is_a?(Puppet::Parser::AST::Name) and h[:lval].is_a?(Puppet::Parser::AST::Name) and h[:operator]=="==" - }.returns(aststub) - Puppet::Parser::AST::BooleanOperator.expects(:new).with { - |h| h[:rval]==aststub and h[:lval]==aststub and h[:operator]=="and" - } - @parser.parse("if (1 > 2) and (1 == 2) { $var = 1 }") - end - - it "should raise an error on incorrect expression" do - expect { - @parser.parse("if (1 > 2 > ) or (1 == 2) { $var = 1 }") - }.to raise_error(Puppet::ParseError, /Syntax error at '\)'/) - end - - end - - describe "when parsing resource references" do - - it "should not raise syntax errors" do - expect { @parser.parse('exec { test: param => File["a"] }') }.to_not raise_error - end - - it "should not raise syntax errors with multiple references" do - expect { @parser.parse('exec { test: param => File["a","b"] }') }.to_not raise_error - end - - it "should create an ast::ResourceReference" do - # NOTE: In egrammar, type and name are unified immediately to lower case whereas the regular grammar - # keeps the UC name in some contexts - it gets downcased later as the name of the type is in lower case. - # - Puppet::Parser::AST::ResourceReference.expects(:new).with { |arg| - arg[:line]==1 and arg[:pos] ==25 and arg[:type]=="file" and arg[:title].is_a?(Puppet::Parser::AST::ASTArray) - } - @parser.parse('exec { test: command => File["a","b"] }') - end - end - - describe "when parsing resource overrides" do - - it "should not raise syntax errors" do - expect { @parser.parse('Resource["title"] { param => value }') }.to_not raise_error - end - - it "should not raise syntax errors with multiple overrides" do - expect { @parser.parse('Resource["title1","title2"] { param => value }') }.to_not raise_error - end - - it "should create an ast::ResourceOverride" do - ro = @parser.parse('Resource["title1","title2"] { param => value }').code[0] - ro.should be_a(Puppet::Parser::AST::ResourceOverride) - ro.line.should == 1 - ro.object.should be_a(Puppet::Parser::AST::ResourceReference) - ro.parameters[0].should be_a(Puppet::Parser::AST::ResourceParam) - end - - end - - describe "when parsing if statements" do - - it "should not raise errors with empty if" do - expect { @parser.parse("if true { }") }.to_not raise_error - end - - it "should not raise errors with empty else" do - expect { @parser.parse("if false { notice('if') } else { }") }.to_not raise_error - end - - it "should not raise errors with empty if and else" do - expect { @parser.parse("if false { } else { }") }.to_not raise_error - end - - it "should create a nop node for empty branch" do - Puppet::Parser::AST::Nop.expects(:new).twice - @parser.parse("if true { }") - end - - it "should create a nop node for empty else branch" do - Puppet::Parser::AST::Nop.expects(:new) - @parser.parse("if true { notice('test') } else { }") - end - - it "should build a chain of 'ifs' if there's an 'elsif'" do - expect { @parser.parse(<<-PP) }.to_not raise_error - if true { notice('test') } elsif true {} else { } - PP - end - - end - - describe "when parsing function calls" do - it "should not raise errors with no arguments" do - expect { @parser.parse("tag()") }.to_not raise_error - end - - it "should not raise errors with rvalue function with no args" do - expect { @parser.parse("$a = template()") }.to_not raise_error - end - - it "should not raise errors with arguments" do - expect { @parser.parse("notice(1)") }.to_not raise_error - end - - it "should not raise errors with multiple arguments" do - expect { @parser.parse("notice(1,2)") }.to_not raise_error - end - - it "should not raise errors with multiple arguments and a trailing comma" do - expect { @parser.parse("notice(1,2,)") }.to_not raise_error - end - - end - - describe "when parsing arrays" do - it "should parse an array" do - expect { @parser.parse("$a = [1,2]") }.to_not raise_error - end - - it "should not raise errors with a trailing comma" do - expect { @parser.parse("$a = [1,2,]") }.to_not raise_error - end - - it "should accept an empty array" do - expect { @parser.parse("$var = []\n") }.to_not raise_error - end - end - - describe "when parsing classes" do - before :each do - @krt = Puppet::Resource::TypeCollection.new("development") - @classic_parser = Puppet::Parser::Parser.new "development" - @parser = Puppet::Parser::EParserAdapter.new(@classic_parser) - @classic_parser.stubs(:known_resource_types).returns @krt - end - - it "should not create new classes" do - @parser.parse("class foobar {}").code[0].should be_a(Puppet::Parser::AST::Hostclass) - @krt.hostclass("foobar").should be_nil - end - - it "should correctly set the parent class when one is provided" do - @parser.parse("class foobar inherits yayness {}").code[0].instantiate('')[0].parent.should == "yayness" - end - - it "should correctly set the parent class for multiple classes at a time" do - statements = @parser.parse("class foobar inherits yayness {}\nclass boo inherits bar {}").code - statements[0].instantiate('')[0].parent.should == "yayness" - statements[1].instantiate('')[0].parent.should == "bar" - end - - it "should define the code when some is provided" do - @parser.parse("class foobar { $var = val }").code[0].code.should_not be_nil - end - - it "should accept parameters with trailing comma" do - @parser.parse("file { '/example': ensure => file, }").should be - end - - it "should accept parametrized classes with trailing comma" do - @parser.parse("class foobar ($var1 = 0,) { $var = val }").code[0].code.should_not be_nil - end - - it "should define parameters when provided" do - foobar = @parser.parse("class foobar($biz,$baz) {}").code[0].instantiate('')[0] - foobar.arguments.should == {"biz" => nil, "baz" => nil} - end - end - - describe "when parsing resources" do - before :each do - @krt = Puppet::Resource::TypeCollection.new("development") - @classic_parser = Puppet::Parser::Parser.new "development" - @parser = Puppet::Parser::EParserAdapter.new(@classic_parser) - @classic_parser.stubs(:known_resource_types).returns @krt - end - - it "should be able to parse class resources" do - @krt.add(Puppet::Resource::Type.new(:hostclass, "foobar", :arguments => {"biz" => nil})) - expect { @parser.parse("class { foobar: biz => stuff }") }.to_not raise_error - end - - it "should correctly mark exported resources as exported" do - @parser.parse("@@file { '/file': }").code[0].exported.should be_true - end - - it "should correctly mark virtual resources as virtual" do - @parser.parse("@file { '/file': }").code[0].virtual.should be_true - end - end - - describe "when parsing nodes" do - it "should be able to parse a node with a single name" do - node = @parser.parse("node foo { }").code[0] - node.should be_a Puppet::Parser::AST::Node - node.names.length.should == 1 - node.names[0].value.should == "foo" - end - - it "should be able to parse a node with two names" do - node = @parser.parse("node foo, bar { }").code[0] - node.should be_a Puppet::Parser::AST::Node - node.names.length.should == 2 - node.names[0].value.should == "foo" - node.names[1].value.should == "bar" - end - - it "should be able to parse a node with three names" do - node = @parser.parse("node foo, bar, baz { }").code[0] - node.should be_a Puppet::Parser::AST::Node - node.names.length.should == 3 - node.names[0].value.should == "foo" - node.names[1].value.should == "bar" - node.names[2].value.should == "baz" - end - end - - it "should fail if trying to collect defaults" do - expect { - @parser.parse("@Port { protocols => tcp }") - }.to raise_error(Puppet::ParseError, /Defaults are not virtualizable/) - end - - context "when parsing collections" do - it "should parse basic collections" do - @parser.parse("Port <| |>").code. - should be_all {|x| x.is_a? Puppet::Parser::AST::Collection } - end - - it "should parse fully qualified collections" do - @parser.parse("Port::Range <| |>").code. - should be_all {|x| x.is_a? Puppet::Parser::AST::Collection } - end - end - - it "should not assign to a fully qualified variable" do - expect { - @parser.parse("$one::two = yay") - }.to raise_error(Puppet::ParseError, /Cannot assign to variables in other namespaces/) - end - - it "should parse assignment of undef" do - tree = @parser.parse("$var = undef") - tree.code.children[0].should be_an_instance_of Puppet::Parser::AST::VarDef - tree.code.children[0].value.should be_an_instance_of Puppet::Parser::AST::Undef - end - - it "should treat classes as case insensitive" do - @classic_parser.known_resource_types.import_ast(@parser.parse("class yayness {}"), '') - @classic_parser.known_resource_types.hostclass('yayness'). - should == @classic_parser.find_hostclass("", "YayNess") - end - - it "should treat defines as case insensitive" do - @classic_parser.known_resource_types.import_ast(@parser.parse("define funtest {}"), '') - @classic_parser.known_resource_types.hostclass('funtest'). - should == @classic_parser.find_hostclass("", "fUntEst") - end - context "when parsing method calls" do - it "should parse method call with one param lambda" do - 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.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.each |$a,$b=1|{ debug $a }") }.to_not raise_error - end - it "should parse method call without lambda (statement)" do - expect { @parser.parse("$a.each") }.to_not raise_error - end - it "should parse method call without lambda (expression)" do - 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.each") }.to_not raise_error - end - it "hasharrayaccess should be allowed" do - expect { @parser.parse("$a[0][1].each") }.to_not raise_error - end - it "quoted text should be allowed" do - 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]}.each") }.to_not raise_error - end - it "function call should be allowed" do - 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 - end - end - end -end diff --git a/spec/unit/parser/files_spec.rb b/spec/unit/parser/files_spec.rb index 3eb43b276..020ce740b 100755 --- a/spec/unit/parser/files_spec.rb +++ b/spec/unit/parser/files_spec.rb @@ -12,6 +12,25 @@ describe Puppet::Parser::Files do @basepath = make_absolute("/somepath") end + describe "when searching for files" do + it "should return fully-qualified files directly" do + Puppet::Parser::Files.expects(:modulepath).never + Puppet::Parser::Files.find_file(@basepath + "/my/file", environment).should == @basepath + "/my/file" + end + + it "should return the first found file" do + mod = mock 'module' + mod.expects(:file).returns("/one/mymod/files/myfile") + environment.expects(:module).with("mymod").returns mod + + Puppet::Parser::Files.find_file("mymod/myfile", environment).should == "/one/mymod/files/myfile" + end + + it "should return nil if template is not found" do + Puppet::Parser::Files.find_file("foomod/myfile", environment).should be_nil + end + end + describe "when searching for templates" do it "should return fully-qualified templates directly" do Puppet::Parser::Files.expects(:modulepath).never diff --git a/spec/unit/parser/functions/contain_spec.rb b/spec/unit/parser/functions/contain_spec.rb index 3150e0c8e..2a5aa57c7 100644 --- a/spec/unit/parser/functions/contain_spec.rb +++ b/spec/unit/parser/functions/contain_spec.rb @@ -3,11 +3,15 @@ require 'spec_helper' require 'puppet_spec/compiler' require 'puppet/parser/functions' require 'matchers/containment_matchers' +require 'matchers/resource' require 'matchers/include_in_order' +require 'unit/parser/functions/shared' + describe 'The "contain" function' do include PuppetSpec::Compiler include ContainmentMatchers + include Matchers::Resource it "includes the class" do catalog = compile_to_catalog(<<-MANIFEST) @@ -25,6 +29,41 @@ describe 'The "contain" function' do expect(catalog.classes).to include("contained") end + it "includes the class when using a fully qualified anchored name" 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 "ensures that the edge is with the correct class" do + catalog = compile_to_catalog(<<-MANIFEST) + class outer { + class named { } + contain named + } + + class named { } + + include named + include outer + MANIFEST + + expect(catalog).to have_resource("Class[Named]") + expect(catalog).to have_resource("Class[Outer]") + expect(catalog).to have_resource("Class[Outer::Named]") + expect(catalog).to contain_class("outer::named").in("outer") + end + it "makes the class contained in the current class" do catalog = compile_to_catalog(<<-MANIFEST) class contained { @@ -182,4 +221,16 @@ describe 'The "contain" function' do ) end end + + describe "When the future parser is in use" do + require 'puppet/pops' + before(:each) do + Puppet[:parser] = 'future' + compiler = Puppet::Parser::Compiler.new(Puppet::Node.new("foo")) + @scope = Puppet::Parser::Scope.new(compiler) + end + + it_should_behave_like 'all functions transforming relative to absolute names', :function_contain + it_should_behave_like 'an inclusion function, regardless of the type of class reference,', :contain + end end diff --git a/spec/unit/parser/functions/create_resources_spec.rb b/spec/unit/parser/functions/create_resources_spec.rb index 79ed02f22..3e7bd8015 100755 --- a/spec/unit/parser/functions/create_resources_spec.rb +++ b/spec/unit/parser/functions/create_resources_spec.rb @@ -49,7 +49,7 @@ describe 'function for dynamically creating resources' do end it 'should be able to add exported resources' do - catalog = compile_to_catalog("create_resources('@@file', {'/etc/foo'=>{'ensure'=>'present'}})") + catalog = compile_to_catalog("create_resources('@@file', {'/etc/foo'=>{'ensure'=>'present'}}) realize(File['/etc/foo'])") catalog.resource(:file, "/etc/foo")['ensure'].should == 'present' catalog.resource(:file, "/etc/foo").exported.should == true end @@ -202,5 +202,12 @@ describe 'function for dynamically creating resources' do catalog.resource(:notify, "test")['message'].should == 'two' catalog.resource(:class, "bar").should_not be_nil end + + it 'should fail with a correct error message if the syntax of an imported file is incorrect' do + expect{ + Puppet[:modulepath] = my_fixture_dir + compile_to_catalog('include foo') + }.to raise_error(Puppet::Error, /Syntax error at.*/) + end end end diff --git a/spec/unit/parser/functions/digest_spec.rb b/spec/unit/parser/functions/digest_spec.rb new file mode 100755 index 000000000..e3c0762d4 --- /dev/null +++ b/spec/unit/parser/functions/digest_spec.rb @@ -0,0 +1,31 @@ +#!/usr/bin/env rspec +require 'spec_helper' + +describe "the digest function", :uses_checksums => true do + before :all do + Puppet::Parser::Functions.autoloader.loadall + end + + before :each do + n = Puppet::Node.new('unnamed') + c = Puppet::Parser::Compiler.new(n) + @scope = Puppet::Parser::Scope.new(c) + end + + it "should exist" do + Puppet::Parser::Functions.function("digest").should == "function_digest" + end + + with_digest_algorithms do + it "should use the proper digest function" do + result = @scope.function_digest([plaintext]) + result.should(eql( checksum )) + end + + it "should only accept one parameter" do + expect do + @scope.function_digest(['foo', 'bar']) + end.to raise_error(ArgumentError) + end + end +end diff --git a/spec/unit/parser/functions/file_spec.rb b/spec/unit/parser/functions/file_spec.rb index 34d12e2f2..c5f157300 100755 --- a/spec/unit/parser/functions/file_spec.rb +++ b/spec/unit/parser/functions/file_spec.rb @@ -13,10 +13,6 @@ describe "the 'file' function" do let :compiler do Puppet::Parser::Compiler.new(node) end let :scope do Puppet::Parser::Scope.new(compiler) end - it "should exist" do - Puppet::Parser::Functions.function("file").should == "function_file" - end - def with_file_content(content) path = tmpfile('file-function') file = File.new(path, 'w') @@ -31,7 +27,17 @@ describe "the 'file' function" do end end - it "should return the first file if given two files" do + it "should read a file from a module path" do + with_file_content('file content') do |name| + mod = mock 'module' + mod.stubs(:file).with('myfile').returns(name) + compiler.environment.stubs(:module).with('mymod').returns(mod) + + scope.function_file(['mymod/myfile']).should == 'file content' + end + end + + it "should return the first file if given two files with absolute paths" do with_file_content('one') do |one| with_file_content('two') do |two| scope.function_file([one, two]).should == "one" @@ -39,6 +45,43 @@ describe "the 'file' function" do end end + it "should return the first file if given two files with module paths" do + with_file_content('one') do |one| + with_file_content('two') do |two| + mod = mock 'module' + compiler.environment.expects(:module).with('mymod').returns(mod) + mod.expects(:file).with('one').returns(one) + mod.stubs(:file).with('two').returns(two) + + scope.function_file(['mymod/one','mymod/two']).should == 'one' + end + end + end + + it "should return the first file if given two files with mixed paths, absolute first" do + with_file_content('one') do |one| + with_file_content('two') do |two| + mod = mock 'module' + compiler.environment.stubs(:module).with('mymod').returns(mod) + mod.stubs(:file).with('two').returns(two) + + scope.function_file([one,'mymod/two']).should == 'one' + end + end + end + + it "should return the first file if given two files with mixed paths, module first" do + with_file_content('one') do |one| + with_file_content('two') do |two| + mod = mock 'module' + compiler.environment.expects(:module).with('mymod').returns(mod) + mod.stubs(:file).with('two').returns(two) + + scope.function_file(['mymod/two',one]).should == 'two' + end + end + end + it "should not fail when some files are absent" do expect { with_file_content('one') do |one| diff --git a/spec/unit/parser/functions/include_spec.rb b/spec/unit/parser/functions/include_spec.rb index c1a5cbd5c..3fa0da35d 100755 --- a/spec/unit/parser/functions/include_spec.rb +++ b/spec/unit/parser/functions/include_spec.rb @@ -1,5 +1,6 @@ #! /usr/bin/env ruby require 'spec_helper' +require 'unit/parser/functions/shared' describe "the 'include' function" do before :all do @@ -46,6 +47,19 @@ describe "the 'include' function" do it "should raise if the class is not found" do @scope.stubs(:source).returns(true) - expect { @scope.function_include(["nosuchclass"]) }.to raise_error Puppet::Error + expect { @scope.function_include(["nosuchclass"]) }.to raise_error(Puppet::Error) + end + + describe "When the future parser is in use" do + require 'puppet/pops' + require 'puppet_spec/compiler' + include PuppetSpec::Compiler + + before(:each) do + Puppet[:parser] = 'future' + end + + it_should_behave_like 'all functions transforming relative to absolute names', :function_include + it_should_behave_like 'an inclusion function, regardless of the type of class reference,', :include end end diff --git a/spec/unit/parser/functions/realize_spec.rb b/spec/unit/parser/functions/realize_spec.rb index 9f53f5a76..79e5eb155 100755 --- a/spec/unit/parser/functions/realize_spec.rb +++ b/spec/unit/parser/functions/realize_spec.rb @@ -1,53 +1,61 @@ -#! /usr/bin/env ruby require 'spec_helper' +require 'matchers/resource' +require 'puppet_spec/compiler' describe "the realize function" do - before :all do - Puppet::Parser::Functions.autoloader.loadall - end + include Matchers::Resource + include PuppetSpec::Compiler - before :each do - @collector = stub_everything 'collector' - node = Puppet::Node.new('localhost') - @compiler = Puppet::Parser::Compiler.new(node) - @scope = Puppet::Parser::Scope.new(@compiler) - @compiler.stubs(:add_collection).with(@collector) - end + it "realizes a single, referenced resource" do + catalog = compile_to_catalog(<<-EOM) + @notify { testing: } + realize(Notify[testing]) + EOM - it "should exist" do - Puppet::Parser::Functions.function("realize").should == "function_realize" + expect(catalog).to have_resource("Notify[testing]") end - it "should create a Collector when called" do - - Puppet::Parser::Collector.expects(:new).returns(@collector) + it "realizes multiple resources" do + catalog = compile_to_catalog(<<-EOM) + @notify { testing: } + @notify { other: } + realize(Notify[testing], Notify[other]) + EOM - @scope.function_realize(["test"]) + expect(catalog).to have_resource("Notify[testing]") + expect(catalog).to have_resource("Notify[other]") end - it "should assign the passed-in resources to the collector" do - Puppet::Parser::Collector.stubs(:new).returns(@collector) + it "realizes resources provided in arrays" do + catalog = compile_to_catalog(<<-EOM) + @notify { testing: } + @notify { other: } + realize([Notify[testing], [Notify[other]]]) + EOM - @collector.expects(:resources=).with(["test"]) - - @scope.function_realize(["test"]) + expect(catalog).to have_resource("Notify[testing]") + expect(catalog).to have_resource("Notify[other]") end - it "should flatten the resources assigned to the collector" do - Puppet::Parser::Collector.stubs(:new).returns(@collector) - - @collector.expects(:resources=).with(["test"]) - - @scope.function_realize([["test"]]) + it "fails when the resource does not exist" do + expect do + compile_to_catalog(<<-EOM) + realize(Notify[missing]) + EOM + end.to raise_error(Puppet::Error, /Failed to realize/) end - it "should let the compiler know this collector" do - Puppet::Parser::Collector.stubs(:new).returns(@collector) - @collector.stubs(:resources=).with(["test"]) - - @compiler.expects(:add_collection).with(@collector) - - @scope.function_realize(["test"]) + it "fails when no parameters given" do + expect do + compile_to_catalog(<<-EOM) + realize() + EOM + end.to raise_error(Puppet::Error, /Wrong number of arguments/) end + it "silently does nothing when an empty array of resources is given" do + compile_to_catalog(<<-EOM) + realize([]) + EOM + end end diff --git a/spec/unit/parser/functions/require_spec.rb b/spec/unit/parser/functions/require_spec.rb index 72c3f9f5f..f0b4fcc28 100755 --- a/spec/unit/parser/functions/require_spec.rb +++ b/spec/unit/parser/functions/require_spec.rb @@ -1,5 +1,6 @@ #! /usr/bin/env ruby require 'spec_helper' +require 'unit/parser/functions/shared' describe "the require function" do before :all do @@ -26,13 +27,13 @@ describe "the require function" do end it "should delegate to the 'include' puppet function" do - @scope.expects(:function_include).with(["myclass"]) + @scope.compiler.expects(:evaluate_classes).with(["myclass"], @scope, false) @scope.function_require(["myclass"]) end - it "should set the 'require' prarameter on the resource to a resource reference" do - @scope.stubs(:function_include) + it "should set the 'require' parameter on the resource to a resource reference" do + @scope.compiler.stubs(:evaluate_classes) @scope.function_require(["myclass"]) @resource["require"].should be_instance_of(Array) @@ -40,7 +41,7 @@ describe "the require function" do end it "should lookup the absolute class path" do - @scope.stubs(:function_include) + @scope.compiler.stubs(:evaluate_classes) @scope.expects(:find_hostclass).with("myclass").returns(@klass) @klass.expects(:name).returns("myclass") @@ -49,7 +50,7 @@ describe "the require function" do end it "should append the required class to the require parameter" do - @scope.stubs(:function_include) + @scope.compiler.stubs(:evaluate_classes) one = Puppet::Resource.new(:file, "/one") @resource[:require] = one @@ -58,4 +59,17 @@ describe "the require function" do @resource[:require].should be_include(one) @resource[:require].detect { |r| r.to_s == "Class[Myclass]" }.should be_instance_of(Puppet::Resource) end + + describe "When the future parser is in use" do + require 'puppet/pops' + require 'puppet_spec/compiler' + include PuppetSpec::Compiler + + before(:each) do + Puppet[:parser] = 'future' + end + + it_should_behave_like 'all functions transforming relative to absolute names', :function_require + it_should_behave_like 'an inclusion function, regardless of the type of class reference,', :require + end end diff --git a/spec/unit/parser/functions/search_spec.rb b/spec/unit/parser/functions/search_spec.rb index b2c042b04..54054bd6a 100755 --- a/spec/unit/parser/functions/search_spec.rb +++ b/spec/unit/parser/functions/search_spec.rb @@ -20,4 +20,9 @@ describe "the 'search' function" do scope.expects(:add_namespace).with("who") scope.function_search(["where", "what", "who"]) end + + it "is deprecated" do + Puppet.expects(:deprecation_warning).with("The 'search' function is deprecated. See http://links.puppetlabs.com/search-function-deprecation") + scope.function_search(['wat']) + end end diff --git a/spec/unit/parser/functions/shared.rb b/spec/unit/parser/functions/shared.rb new file mode 100644 index 000000000..f5adcd811 --- /dev/null +++ b/spec/unit/parser/functions/shared.rb @@ -0,0 +1,82 @@ +shared_examples_for 'all functions transforming relative to absolute names' do |func_method| + + it 'transforms relative names to absolute' do + @scope.compiler.expects(:evaluate_classes).with(["::myclass"], @scope, false) + @scope.send(func_method, ["myclass"]) + end + + it 'accepts a Class[name] type' do + @scope.compiler.expects(:evaluate_classes).with(["::myclass"], @scope, false) + @scope.send(func_method, [Puppet::Pops::Types::TypeFactory.host_class('myclass')]) + end + + it 'accepts a Resource[class, name] type' do + @scope.compiler.expects(:evaluate_classes).with(["::myclass"], @scope, false) + @scope.send(func_method, [Puppet::Pops::Types::TypeFactory.resource('class', 'myclass')]) + end + + it 'raises and error for unspecific Class' do + expect { + @scope.send(func_method, [Puppet::Pops::Types::TypeFactory.host_class()]) + }.to raise_error(ArgumentError, /Cannot use an unspecific Class\[\] Type/) + end + + it 'raises and error for Resource that is not of class type' do + expect { + @scope.send(func_method, [Puppet::Pops::Types::TypeFactory.resource('file')]) + }.to raise_error(ArgumentError, /Cannot use a Resource\[file\] where a Resource\['class', name\] is expected/) + end + + it 'raises and error for Resource that is unspecific' do + expect { + @scope.send(func_method, [Puppet::Pops::Types::TypeFactory.resource()]) + }.to raise_error(ArgumentError, /Cannot use an unspecific Resource\[\] where a Resource\['class', name\] is expected/) + end + + it 'raises and error for Resource[class] that is unspecific' do + expect { + @scope.send(func_method, [Puppet::Pops::Types::TypeFactory.resource('class')]) + }.to raise_error(ArgumentError, /Cannot use an unspecific Resource\['class'\] where a Resource\['class', name\] is expected/) + end + +end + +shared_examples_for 'an inclusion function, regardless of the type of class reference,' do |function| + + it "and #{function} a class absolutely, even when a relative namespaced class of the same name is present" do + catalog = compile_to_catalog(<<-MANIFEST) + class foo { + class bar { } + #{function} bar + } + class bar { } + include foo + MANIFEST + expect(catalog.classes).to include('foo','bar') + end + + it "and #{function} a class absolutely by Class['type'] reference" do + catalog = compile_to_catalog(<<-MANIFEST) + class foo { + class bar { } + #{function} Class['bar'] + } + class bar { } + include foo + MANIFEST + expect(catalog.classes).to include('foo','bar') + end + + it "and #{function} a class absolutely by Resource['type','title'] reference" do + catalog = compile_to_catalog(<<-MANIFEST) + class foo { + class bar { } + #{function} Resource['class','bar'] + } + class bar { } + include foo + MANIFEST + expect(catalog.classes).to include('foo','bar') + end + +end diff --git a/spec/unit/parser/functions_spec.rb b/spec/unit/parser/functions_spec.rb index b3c04d1af..3c6266752 100755 --- a/spec/unit/parser/functions_spec.rb +++ b/spec/unit/parser/functions_spec.rb @@ -38,7 +38,7 @@ describe Puppet::Parser::Functions do it "instruments the function to profile the execution" do messages = [] - Puppet::Util::Profiler.current = Puppet::Util::Profiler::WallClock.new(proc { |msg| messages << msg }, "id") + Puppet::Util::Profiler.add_profiler(Puppet::Util::Profiler::WallClock.new(proc { |msg| messages << msg }, "id")) Puppet::Parser::Functions.newfunction("name", :type => :rvalue) { |args| } callable_functions_from(function_module).function_name([]) @@ -85,10 +85,7 @@ describe Puppet::Parser::Functions do end describe "when calling function to test arity" do - let(:function_module) { Module.new } - before do - Puppet::Parser::Functions.stubs(:environment_module).returns(function_module) - end + let(:function_module) { Puppet::Parser::Functions.environment_module(Puppet.lookup(:current_environment)) } it "should raise an error if the function is called with too many arguments" do Puppet::Parser::Functions.newfunction("name", :arity => 2) { |args| } diff --git a/spec/unit/parser/lexer_spec.rb b/spec/unit/parser/lexer_spec.rb index 62234e214..f0f10e9f3 100755 --- a/spec/unit/parser/lexer_spec.rb +++ b/spec/unit/parser/lexer_spec.rb @@ -279,7 +279,8 @@ describe Puppet::Parser::Lexer::TOKENS[:NAME] do it "should return itself and the value if the matched term is not a keyword" do Puppet::Parser::Lexer::KEYWORDS.expects(:lookup).returns(nil) - @token.convert(stub("lexer"), "myval").should == [Puppet::Parser::Lexer::TOKENS[:NAME], "myval"] + lexer = stub("lexer") + @token.convert(lexer, "myval").should == [Puppet::Parser::Lexer::TOKENS[:NAME], "myval"] end it "should return the keyword token and the value if the matched term is a keyword" do @@ -845,6 +846,14 @@ describe "Puppet::Parser::Lexer in the old tests" do end end +describe 'Puppet::Parser::Lexer handles reserved words' do + ['function', 'private', 'attr', 'type'].each do |reserved_bare_word| + it "by delivering '#{reserved_bare_word}' as a bare word" do + expect(tokens_scanned_from(reserved_bare_word)).to eq([[:NAME, {:value=>reserved_bare_word, :line => 1}]]) + end + end +end + describe "Puppet::Parser::Lexer in the old tests when lexing example files" do my_fixtures('*.pp') do |file| it "should correctly lex #{file}" do diff --git a/spec/unit/parser/methods/map_spec.rb b/spec/unit/parser/methods/map_spec.rb deleted file mode 100644 index 7f8e79789..000000000 --- a/spec/unit/parser/methods/map_spec.rb +++ /dev/null @@ -1,184 +0,0 @@ -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 an enumerable type (multiplying each value by 2)' do - catalog = compile_to_catalog(<<-MANIFEST) - $a = Integer[1,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 an integer (multiply each by 3)' do - catalog = compile_to_catalog(<<-MANIFEST) - 3.map |$x|{ $x*3}.each |$v|{ - file { "/file_$v": ensure => present } - } - MANIFEST - - catalog.resource(:file, "/file_0")['ensure'].should == 'present' - catalog.resource(:file, "/file_3")['ensure'].should == 'present' - catalog.resource(:file, "/file_6")['ensure'].should == 'present' - end - - it 'map on a string' do - catalog = compile_to_catalog(<<-MANIFEST) - $a = {a=>x, b=>y} - "ab".map |$x|{$a[$x]}.each |$v|{ - file { "/file_$v": ensure => present } - } - MANIFEST - - catalog.resource(:file, "/file_x")['ensure'].should == 'present' - catalog.resource(:file, "/file_y")['ensure'].should == 'present' - end - - it 'map on an array (multiplying value by 10 in even index position)' do - catalog = compile_to_catalog(<<-MANIFEST) - $a = [1,2,3] - $a.map |$i, $x|{ if $i % 2 == 0 {$x} else {$x*10}}.each |$v|{ - file { "/file_$v": ensure => present } - } - MANIFEST - - catalog.resource(:file, "/file_1")['ensure'].should == 'present' - catalog.resource(:file, "/file_20")['ensure'].should == 'present' - catalog.resource(:file, "/file_3")['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 'map on a hash selecting keys - using two block parameters' do - catalog = compile_to_catalog(<<-MANIFEST) - $a = {'a'=>1,'b'=>2,'c'=>3} - $a.map |$k,$v|{ 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 - - it 'each on a hash selecting value - using two bloc parameters' do - catalog = compile_to_catalog(<<-MANIFEST) - $a = {'a'=>1,'b'=>2,'c'=>3} - $a.map |$k,$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 - - 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.something")['ensure'].should == 'present' - end - end - - context 'map checks arguments and' do - it 'raises an error when block has more than 2 argument' do - expect do - compile_to_catalog(<<-MANIFEST) - [1].map |$index, $x, $yikes|{ } - MANIFEST - end.to raise_error(Puppet::Error, /block must define at most two parameters/) - end - - it 'raises an error when block has fewer than 1 argument' do - expect do - compile_to_catalog(<<-MANIFEST) - [1].map || { } - MANIFEST - end.to raise_error(Puppet::Error, /block must define at least one parameter/) - 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/shared.rb b/spec/unit/parser/methods/shared.rb deleted file mode 100644 index 42cfd2359..000000000 --- a/spec/unit/parser/methods/shared.rb +++ /dev/null @@ -1,45 +0,0 @@ - -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]}": } } - MANIFEST - - catalog.resource(:notify, "a 1").should_not be_nil - end -end - -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) - 3.14.#{func} |$v| { } - MANIFEST - end.to raise_error(Puppet::Error, /must be something enumerable/) - end - - it 'raises an error when called with any parameters besides a block' do - expect do - compile_to_catalog(<<-MANIFEST) - [1].#{func}(1) |$v| { } - MANIFEST - end.to raise_error(Puppet::Error, /Wrong number of arguments/) - end - - it 'raises an error when called without a block' do - expect do - compile_to_catalog(<<-MANIFEST) - [1].#{func}() - MANIFEST - end.to raise_error(Puppet::Error, /Wrong number of arguments/) - end - - it 'raises an error when called without a block' do - expect do - compile_to_catalog(<<-MANIFEST) - [1].#{func}(1) - MANIFEST - end.to raise_error(Puppet::Error, /must be a parameterized block/) - end -end diff --git a/spec/unit/parser/type_loader_spec.rb b/spec/unit/parser/type_loader_spec.rb index 659ffa942..5454528a7 100755 --- a/spec/unit/parser/type_loader_spec.rb +++ b/spec/unit/parser/type_loader_spec.rb @@ -3,7 +3,6 @@ require 'spec_helper' require 'puppet/parser/type_loader' require 'puppet/parser/parser_factory' -require 'puppet/parser/e_parser_adapter' require 'puppet_spec/modules' require 'puppet_spec/files' diff --git a/spec/unit/pops/benchmark_spec.rb b/spec/unit/pops/benchmark_spec.rb index 03c2e743d..462c03947 100644 --- a/spec/unit/pops/benchmark_spec.rb +++ b/spec/unit/pops/benchmark_spec.rb @@ -118,7 +118,7 @@ $a = "interpolate ${foo} and stuff" end context "Measure Evaluator" do - let(:parser) { Puppet::Pops::Parser::EvaluatingParser::Transitional.new } + let(:parser) { Puppet::Pops::Parser::EvaluatingParser.new } let(:node) { 'node.example.com' } let(:scope) { s = create_test_scope_for_node(node); s } it "evaluator", :profile => true do diff --git a/spec/unit/pops/binder/bindings_composer_spec.rb b/spec/unit/pops/binder/bindings_composer_spec.rb index 93bc44722..d8e4b6f9c 100644 --- a/spec/unit/pops/binder/bindings_composer_spec.rb +++ b/spec/unit/pops/binder/bindings_composer_spec.rb @@ -34,31 +34,33 @@ describe 'BinderComposer' do it 'should load everything without errors' do Puppet.settings[:confdir] = config_directory Puppet.settings[:libdir] = File.join(config_directory, 'lib') - Puppet.settings[:modulepath] = File.join(config_directory, 'modules') - # this ensure the binder is active at the right time - # (issues with getting a /dev/null path for "confdir" / "libdir") - raise "Binder not active" unless scope.compiler.is_binder_active? - diagnostics = diag - composer = Puppet::Pops::Binder::BindingsComposer.new() - the_scope = scope - the_scope['fqdn'] = 'localhost' - the_scope['environment'] = 'production' - layered_bindings = composer.compose(scope) - # puts Puppet::Pops::Binder::BindingsModelDumper.new().dump(layered_bindings) - binder = Puppet::Pops::Binder::Binder.new(layered_bindings) - injector = Puppet::Pops::Binder::Injector.new(binder) - expect(injector.lookup(scope, 'awesome_x')).to be == 'golden' - expect(injector.lookup(scope, 'good_x')).to be == 'golden' - expect(injector.lookup(scope, 'rotten_x')).to be == nil - expect(injector.lookup(scope, 'the_meaning_of_life')).to be == 42 - expect(injector.lookup(scope, 'has_funny_hat')).to be == 'the pope' - expect(injector.lookup(scope, 'all your base')).to be == 'are belong to us' - expect(injector.lookup(scope, 'env_meaning_of_life')).to be == 'production thinks it is 42' - expect(injector.lookup(scope, '::quick::brown::fox')).to be == 'echo: quick brown fox' + Puppet.override(:environments => Puppet::Environments::Static.new(Puppet::Node::Environment.create(:production, [File.join(config_directory, 'modules')]))) do + # this ensure the binder is active at the right time + # (issues with getting a /dev/null path for "confdir" / "libdir") + raise "Binder not active" unless scope.compiler.is_binder_active? + + diagnostics = diag + composer = Puppet::Pops::Binder::BindingsComposer.new() + the_scope = scope + the_scope['fqdn'] = 'localhost' + the_scope['environment'] = 'production' + layered_bindings = composer.compose(scope) + # puts Puppet::Pops::Binder::BindingsModelDumper.new().dump(layered_bindings) + binder = Puppet::Pops::Binder::Binder.new(layered_bindings) + injector = Puppet::Pops::Binder::Injector.new(binder) + expect(injector.lookup(scope, 'awesome_x')).to be == 'golden' + expect(injector.lookup(scope, 'good_x')).to be == 'golden' + expect(injector.lookup(scope, 'rotten_x')).to be == nil + expect(injector.lookup(scope, 'the_meaning_of_life')).to be == 42 + expect(injector.lookup(scope, 'has_funny_hat')).to be == 'the pope' + expect(injector.lookup(scope, 'all your base')).to be == 'are belong to us' + expect(injector.lookup(scope, 'env_meaning_of_life')).to be == 'production thinks it is 42' + expect(injector.lookup(scope, '::quick::brown::fox')).to be == 'echo: quick brown fox' + end end end # TODO: test error conditions (see BinderConfigChecker for what to test) -end
\ No newline at end of file +end diff --git a/spec/unit/pops/binder/injector_spec.rb b/spec/unit/pops/binder/injector_spec.rb index 32eba3260..b62ceaeb9 100644 --- a/spec/unit/pops/binder/injector_spec.rb +++ b/spec/unit/pops/binder/injector_spec.rb @@ -101,14 +101,16 @@ describe 'Injector' do let(:bindings) { factory.named_bindings('test') } let(:scope) { null_scope()} - let(:duck_type) { type_factory.ruby(InjectorSpecModule::TestDuck) } - let(:binder) { Puppet::Pops::Binder::Binder } let(:lbinder) do binder.new(layered_bindings) end + def duck_type + # create distinct instances + type_factory.ruby(InjectorSpecModule::TestDuck) + end let(:layered_bindings) { factory.layered_bindings(test_layer_with_bindings(bindings.model)) } @@ -528,7 +530,7 @@ describe 'Injector' do expect { the_ducks = injector(lbinder).lookup(scope, hash_of_duck, "donalds_nephews") - }.to_not raise_error(/Duplicate key/) + }.to_not raise_error end it "should produce detailed type error message" do @@ -592,11 +594,11 @@ describe 'Injector' do it "should fail attempts to append, perform uniq or flatten on type incompatible multibind hash" do hash_of_integer = type_factory.hash_of(type_factory.integer()) ids = ["ducks1", "ducks2", "ducks3"] - mb = bindings.multibind(ids[0]).type(hash_of_integer).name('broken_family0') + mb = bindings.multibind(ids[0]).type(hash_of_integer.copy).name('broken_family0') mb.producer_options(:conflict_resolution => :append) - mb = bindings.multibind(ids[1]).type(hash_of_integer).name('broken_family1') + mb = bindings.multibind(ids[1]).type(hash_of_integer.copy).name('broken_family1') mb.producer_options(:flatten => :true) - mb = bindings.multibind(ids[2]).type(hash_of_integer).name('broken_family2') + mb = bindings.multibind(ids[2]).type(hash_of_integer.copy).name('broken_family2') mb.producer_options(:uniq => :true) injector = injector(binder.new(factory.layered_bindings(test_layer_with_bindings(bindings.model)))) diff --git a/spec/unit/pops/evaluator/access_ops_spec.rb b/spec/unit/pops/evaluator/access_ops_spec.rb index d0965ad5d..0fa4779a0 100644 --- a/spec/unit/pops/evaluator/access_ops_spec.rb +++ b/spec/unit/pops/evaluator/access_ops_spec.rb @@ -236,7 +236,7 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl/AccessOperator' do it "Tuple parameterization gives an error if parameter is not a type" do expr = fqr('Tuple')['String'] - expect { evaluate(expr)}.to raise_error(/Tuple-Type, Cannot use String where Abstract-Type is expected/) + expect { evaluate(expr)}.to raise_error(/Tuple-Type, Cannot use String where Any-Type is expected/) end it 'produces a varargs Tuple when the last two arguments specify size constraint' do @@ -415,12 +415,12 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl/AccessOperator' do # Ruby Type # it 'creates a Ruby Type instance when applied to a Ruby Type' do - type_expr = fqr('Ruby')['String'] + type_expr = fqr('Runtime')['ruby','String'] tf = Puppet::Pops::Types::TypeFactory expect(evaluate(type_expr)).to eql(tf.ruby_type('String')) # arguments are flattened - type_expr = fqr('Ruby')[['String']] + type_expr = fqr('Runtime')[['ruby', 'String']] expect(evaluate(type_expr)).to eql(tf.ruby_type('String')) end diff --git a/spec/unit/pops/evaluator/comparison_ops_spec.rb b/spec/unit/pops/evaluator/comparison_ops_spec.rb index 29938542e..e3564195c 100644 --- a/spec/unit/pops/evaluator/comparison_ops_spec.rb +++ b/spec/unit/pops/evaluator/comparison_ops_spec.rb @@ -88,10 +88,13 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do end context "of booleans" do - it "true == true == true" do; evaluate(literal(true) == literal(true)).should == true ; end; - it "false == false == true" do; evaluate(literal(false) == literal(false)).should == true ; end; - it "true == false != true" do; evaluate(literal(true) == literal(false)).should == false ; end; - it "false == '' == false" do; evaluate(literal(false) == literal('')).should == false ; end; + it "true == true == true" do; evaluate(literal(true) == literal(true)).should == true ; end; + it "false == false == true" do; evaluate(literal(false) == literal(false)).should == true ; end; + it "true == false != true" do; evaluate(literal(true) == literal(false)).should == false ; end; + it "false == '' == false" do; evaluate(literal(false) == literal('')).should == false ; end; + it "undef == '' == false" do; evaluate(literal(:undef) == literal('')).should == false ; end; + it "undef == undef == true" do; evaluate(literal(:undef) == literal(:undef)).should == true ; end; + it "nil == undef == true" do; evaluate(literal(nil) == literal(:undef)).should == true ; end; end context "of collections" do diff --git a/spec/unit/pops/evaluator/evaluating_parser_spec.rb b/spec/unit/pops/evaluator/evaluating_parser_spec.rb index 210b55b20..5e80e4076 100644 --- a/spec/unit/pops/evaluator/evaluating_parser_spec.rb +++ b/spec/unit/pops/evaluator/evaluating_parser_spec.rb @@ -2,6 +2,7 @@ require 'spec_helper' require 'puppet/pops' require 'puppet/pops/evaluator/evaluator_impl' +require 'puppet/loaders' require 'puppet_spec/pops' require 'puppet_spec/scope' require 'puppet/parser/e4_parser_adapter' @@ -16,18 +17,29 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do before(:each) do Puppet[:strict_variables] = true - # These must be set since the is 3x logic that triggers on these even if the tests are explicit - # about selection of parser and evaluator + # These must be set since the 3x logic switches some behaviors on these even if the tests explicitly + # use the 4x parser and evaluator. # Puppet[:parser] = 'future' - Puppet[:evaluator] = 'future' + # Puppetx cannot be loaded until the correct parser has been set (injector is turned off otherwise) require 'puppetx' + + # Tests needs a known configuration of node/scope/compiler since it parses and evaluates + # snippets as the compiler will evaluate them, butwithout the overhead of compiling a complete + # catalog for each tested expression. + # + @parser = Puppet::Pops::Parser::EvaluatingParser.new + @node = Puppet::Node.new('node.example.com') + @node.environment = Puppet::Node::Environment.create(:testing, []) + @compiler = Puppet::Parser::Compiler.new(@node) + @scope = Puppet::Parser::Scope.new(@compiler) + @scope.source = Puppet::Resource::Type.new(:node, 'node.example.com') + @scope.parent = @compiler.topscope end - let(:parser) { Puppet::Pops::Parser::EvaluatingParser::Transitional.new } - let(:node) { 'node.example.com' } - let(:scope) { s = create_test_scope_for_node(node); s } + let(:parser) { @parser } + let(:scope) { @scope } types = Puppet::Pops::Types::TypeFactory context "When evaluator evaluates literals" do @@ -206,7 +218,7 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do "'a' !~ 'b.*'" => true, '$x = a; a =~ "$x.*"' => true, "a =~ Pattern['a.*']" => true, - "a =~ Regexp['a.*']" => true, + "a =~ Regexp['a.*']" => false, # String is not subtype of Regexp. PUP-957 "$x = /a.*/ a =~ $x" => true, "$x = Pattern['a.*'] a =~ $x" => true, "1 =~ Integer" => true, @@ -240,6 +252,7 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do "/ana/ in bananas" => true, "/xxx/ in bananas" => false, "ANA in bananas" => false, # ANA is a type, not a String + "String[1] in bananas" => false, # Philosophically true though :-) "'ANA' in bananas" => true, "ana in 'BANANAS'" => true, "/ana/ in 'BANANAS'" => false, @@ -278,7 +291,20 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do end { - 'Object' => ['Data', 'Scalar', 'Numeric', 'Integer', 'Float', 'Boolean', 'String', 'Pattern', 'Collection', + "if /(ana)/ in bananas {$1}" => 'ana', + "if /(xyz)/ in bananas {$1} else {$1}" => nil, + "$a = bananas =~ /(ana)/; $b = /(xyz)/ in bananas; $1" => 'ana', + "$a = xyz =~ /(xyz)/; $b = /(ana)/ in bananas; $1" => 'ana', + "if /p/ in [pineapple, bananas] {$0}" => 'p', + "if /b/ in [pineapple, bananas] {$0}" => 'b', + }.each do |source, result| + it "sets match variables for a regexp search using in such that '#{source}' produces '#{result}'" do + parser.evaluate_string(scope, source, __FILE__).should == result + end + end + + { + 'Any' => ['Data', 'Scalar', 'Numeric', 'Integer', 'Float', 'Boolean', 'String', 'Pattern', 'Collection', 'Array', 'Hash', 'CatalogEntry', 'Resource', 'Class', 'Undef', 'File', 'NotYetKnownResourceType'], # Note, Data > Collection is false (so not included) @@ -364,11 +390,21 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do context "on strings requiring boxing to Numeric" do { "'2' + '2'" => 4, + "'-2' + '2'" => 0, + "'- 2' + '2'" => 0, + '"-\t 2" + "2"' => 0, + "'+2' + '2'" => 4, + "'+ 2' + '2'" => 4, "'2.2' + '2.2'" => 4.4, + "'-2.2' + '2.2'" => 0.0, "'0xF7' + '010'" => 0xFF, "'0xF7' + '0x8'" => 0xFF, "'0367' + '010'" => 0xFF, "'012.3' + '010'" => 20.3, + "'-0x2' + '0x4'" => 2, + "'+0x2' + '0x4'" => 6, + "'-02' + '04'" => 2, + "'+02' + '04'" => 6, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result @@ -380,6 +416,10 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do "'0xWTF' + '010'" => :error, "'0x12.3' + '010'" => :error, "'0x12.3' + '010'" => :error, + '"-\n 2" + "2"' => :error, + '"-\v 2" + "2"' => :error, + '"-2\n" + "2"' => :error, + '"-2\n " + "2"' => :error, }.each do |source, result| it "should parse and raise error for '#{source}'" do expect { parser.evaluate_string(scope, source, __FILE__) }.to raise_error(Puppet::ParseError) @@ -395,8 +435,6 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do "$a = 5; $a" => 5, "$a = 5; $b = 6; $a" => 5, "$a = $b = 5; $a == $b" => true, - "$a = [1,2,3]; [x].map |$x| { $a += x; $a }" => [[1,2,3,'x']], - "$a = [a,x,c]; [x].map |$x| { $a -= x; $a }" => [['a','c']], }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result @@ -404,13 +442,11 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do end { - "[a,b,c] = [1,2,3]; $a == 1 and $b == 2 and $c == 3" => :error, - "[a,b,c] = {b=>2,c=>3,a=>1}; $a == 1 and $b == 2 and $c == 3" => :error, - "$a = [1,2,3]; [x].collect |$x| { [a] += x; $a }" => :error, - "$a = [a,x,c]; [x].collect |$x| { [a] -= x; $a }" => :error, + "[a,b,c] = [1,2,3]" => /attempt to assign to 'an Array Expression'/, + "[a,b,c] = {b=>2,c=>3,a=>1}" => /attempt to assign to 'an Array Expression'/, }.each do |source, result| - it "should parse and evaluate the expression '#{source}' to #{result}" do - expect { parser.evaluate_string(scope, source, __FILE__)}.to raise_error(Puppet::ParseError) + it "should parse and evaluate the expression '#{source}' to error with #{result}" do + expect { parser.evaluate_string(scope, source, __FILE__)}.to raise_error(Puppet::ParseError, result) end end end @@ -458,6 +494,9 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do "case Integer { Integer : { no } Type[Integer] : { yes } }" => 'yes', + # supports unfold + "case ringo { + *[paul, john, ringo, george] : { 'beatle' } }" => 'beatle', }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do @@ -467,16 +506,42 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do { "2 ? { 1 => no, 2 => yes}" => 'yes', - "3 ? { 1 => no, 2 => no}" => nil, "3 ? { 1 => no, 2 => no, default => yes }" => 'yes', - "3 ? { 1 => no, default => yes, 3 => no }" => 'yes', + "3 ? { 1 => no, default => yes, 3 => no }" => 'no', + "3 ? { 1 => no, 3 => no, default => yes }" => 'no', + "4 ? { 1 => no, default => yes, 3 => no }" => 'yes', + "4 ? { 1 => no, 3 => no, default => yes }" => 'yes', "'banana' ? { /.*(ana).*/ => $1 }" => 'ana', "[2] ? { Array[String] => yes, Array => yes}" => 'yes', + "ringo ? *[paul, john, ringo, george] => 'beatle'" => 'beatle', }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end + + it 'fails if a selector does not match' do + expect{parser.evaluate_string(scope, "2 ? 3 => 4")}.to raise_error(/No matching entry for selector parameter with value '2'/) + end + end + + context "When evaluator evaluated unfold" do + { + "*[1,2,3]" => [1,2,3], + "*1" => [1], + "*'a'" => ['a'] + }.each do |source, result| + it "should parse and evaluate the expression '#{source}' to #{result}" do + parser.evaluate_string(scope, source, __FILE__).should == result + end + end + + it "should parse and evaluate the expression '*{a=>10, b=>20} to [['a',10],['b',20]]" do + result = parser.evaluate_string(scope, '*{a=>10, b=>20}', __FILE__) + expect(result).to include(['a', 10]) + expect(result).to include(['b', 20]) + end + end context "When evaluator performs [] operations" do @@ -501,6 +566,8 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do "[1,2,3,4][-5,-3]" => [1,2], "[1,2,3,4][-6,-3]" => [1,2], "[1,2,3,4][2,-3]" => [], + "[1,*[2,3],4]" => [1,2,3,4], + "[1,*[2,3],4][1]" => 2, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result @@ -651,24 +718,82 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do adapted_parser = Puppet::Parser::E4ParserAdapter.new adapted_parser.file = __FILE__ ast = adapted_parser.parse(source) - scope.known_resource_types.import_ast(ast, '') - ast.code.safeevaluate(scope).should == 'chocolate' + Puppet.override({:global_scope => scope}, "test") do + scope.known_resource_types.import_ast(ast, '') + ast.code.safeevaluate(scope).should == 'chocolate' + end end # Resource default and override expressions and resource parameter access with [] { + # Properties "notify { id: message=>explicit} Notify[id][message]" => "explicit", "Notify { message=>by_default} notify {foo:} Notify[foo][message]" => "by_default", "notify {foo:} Notify[foo]{message =>by_override} Notify[foo][message]" => "by_override", + # Parameters + "notify { id: withpath=>explicit} Notify[id][withpath]" => "explicit", + "Notify { withpath=>by_default } notify { foo: } Notify[foo][withpath]" => "by_default", + "notify {foo:} + Notify[foo]{withpath=>by_override} + Notify[foo][withpath]" => "by_override", + # Metaparameters "notify { foo: tag => evoe} Notify[foo][tag]" => "evoe", - # Does not produce the defaults for tag + # Does not produce the defaults for tag parameter (title, type or names of scopes) "notify { foo: } Notify[foo][tag]" => nil, + # But a default may be specified on the type + "Notify { tag=>by_default } notify { foo: } Notify[foo][tag]" => "by_default", + "Notify { tag=>by_default } + notify { foo: } + Notify[foo]{ tag=>by_override } + Notify[foo][tag]" => "by_override", }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end + # Virtual and realized resource default and overridden resource parameter access with [] + { + # Properties + "@notify { id: message=>explicit } Notify[id][message]" => "explicit", + "@notify { id: message=>explicit } + realize Notify[id] + Notify[id][message]" => "explicit", + "Notify { message=>by_default } @notify { id: } Notify[id][message]" => "by_default", + "Notify { message=>by_default } + @notify { id: tag=>thisone } + Notify <| tag == thisone |>; + Notify[id][message]" => "by_default", + "@notify { id: } Notify[id]{message=>by_override} Notify[id][message]" => "by_override", + # Parameters + "@notify { id: withpath=>explicit } Notify[id][withpath]" => "explicit", + "Notify { withpath=>by_default } + @notify { id: } + Notify[id][withpath]" => "by_default", + "@notify { id: } + realize Notify[id] + Notify[id]{withpath=>by_override} + Notify[id][withpath]" => "by_override", + # Metaparameters + "@notify { id: tag=>explicit } Notify[id][tag]" => "explicit", + }.each do |source, result| + it "parses and evaluates virtual and realized resources in the expression '#{source}' to #{result}" do + expect(parser.evaluate_string(scope, source, __FILE__)).to eq(result) + end + end + + # Exported resource attributes + { + "@@notify { id: message=>explicit } Notify[id][message]" => "explicit", + "@@notify { id: message=>explicit, tag=>thisone } + Notify <<| tag == thisone |>> + Notify[id][message]" => "explicit", + }.each do |source, result| + it "parses and evaluates exported resources in the expression '#{source}' to #{result}" do + expect(parser.evaluate_string(scope, source, __FILE__)).to eq(result) + end + end + # Resource default and override expressions and resource parameter access error conditions { "notify { xid: message=>explicit} Notify[id][message]" => /Resource not found/, @@ -676,6 +801,7 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do # NOTE: these meta-esque parameters are not recognized as such "notify { id: message=>explicit} Notify[id][title]" => /does not have a parameter called 'title'/, "notify { id: message=>explicit} Notify[id]['type']" => /does not have a parameter called 'type'/, + "notify { id: message=>explicit } Notify[id]{message=>override}" => /'message' is already set on Notify\[id\]/ }.each do |source, result| it "should parse '#{source}' and raise error matching #{result}" do expect { parser.evaluate_string(scope, source, __FILE__)}.to raise_error(result) @@ -713,7 +839,7 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do "!! true" => true, "!! false" => false, "! 'x'" => false, - "! ''" => true, + "! ''" => false, "! undef" => true, "! [a]" => false, "! []" => false, @@ -744,12 +870,15 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do end context "When evaluator performs calls" do + let(:populate) do parser.evaluate_string(scope, "$a = 10 $b = [1,2,3]") end { 'sprintf( "x%iy", $a )' => "x10y", + # unfolds + 'sprintf( *["x%iy", $a] )' => "x10y", '"x%iy".sprintf( $a )' => "x10y", '$b.reduce |$memo,$x| { $memo + $x }' => 6, 'reduce($b) |$memo,$x| { $memo + $x }' => 6, @@ -771,6 +900,69 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do it "provides location information on error in unparenthesized call logic" do expect{parser.evaluate_string(scope, "include non_existing_class", __FILE__)}.to raise_error(Puppet::ParseError, /line 1\:1/) end + + it 'defaults can be given in a lambda and used only when arg is missing' do + env_loader = @compiler.loaders.public_environment_loader + fc = Puppet::Functions.create_function(:test) do + dispatch :test do + param 'Integer', 'count' + required_block_param + end + def test(count, block) + block.call({}, *[].fill(10, 0, count)) + end + end + the_func = fc.new({}, env_loader) + env_loader.add_entry(:function, 'test', the_func, __FILE__) + expect(parser.evaluate_string(scope, "test(1) |$x, $y=20| { $x + $y}")).to eql(30) + expect(parser.evaluate_string(scope, "test(2) |$x, $y=20| { $x + $y}")).to eql(20) + end + + it 'a given undef does not select the default value' do + env_loader = @compiler.loaders.public_environment_loader + fc = Puppet::Functions.create_function(:test) do + dispatch :test do + param 'Any', 'lambda_arg' + required_block_param + end + def test(lambda_arg, block) + block.call({}, lambda_arg) + end + end + the_func = fc.new({}, env_loader) + env_loader.add_entry(:function, 'test', the_func, __FILE__) + + expect(parser.evaluate_string(scope, "test(undef) |$x=20| { $x == undef}")).to eql(true) + end + + it 'a given undef is given as nil' do + env_loader = @compiler.loaders.public_environment_loader + fc = Puppet::Functions.create_function(:assert_no_undef) do + dispatch :assert_no_undef do + param 'Any', 'x' + end + + def assert_no_undef(x) + case x + when Array + return unless x.include?(:undef) + when Hash + return unless x.keys.include?(:undef) || x.values.include?(:undef) + else + return unless x == :undef + end + raise "contains :undef" + end + end + + the_func = fc.new({}, env_loader) + env_loader.add_entry(:function, 'assert_no_undef', the_func, __FILE__) + + expect{parser.evaluate_string(scope, "assert_no_undef(undef)")}.to_not raise_error() + expect{parser.evaluate_string(scope, "assert_no_undef([undef])")}.to_not raise_error() + expect{parser.evaluate_string(scope, "assert_no_undef({undef => 1})")}.to_not raise_error() + expect{parser.evaluate_string(scope, "assert_no_undef({1 => undef})")}.to_not raise_error() + end end context "When evaluator performs string interpolation" do @@ -971,7 +1163,7 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do expect { parser.evaluate_string(scope, src)}.to raise_error(/Cannot parse invalid JSON string/) end - it "parses interpolated heredoc epression" do + it "parses interpolated heredoc expression" do src = <<-CODE $name = 'Fjodor' @("END") @@ -983,12 +1175,6 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do end context "Handles Deprecations and Discontinuations" do - around(:each) do |example| - Puppet.override({:loaders => Puppet::Pops::Loaders.new(Puppet::Node::Environment.create(:testing, []))}, 'test') do - example.run - end - end - it 'of import statements' do source = "\nimport foo" # Error references position 5 at the opening '{' @@ -1002,7 +1188,8 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do source = '1+1 { "title": }' # Error references position 5 at the opening '{' # Set file to nil to make it easier to match with line number (no file name in output) - expect { parser.parse_string(source, nil) }.to raise_error(/Expression is not valid as a resource.*line 1:5/) + expect { parser.evaluate_string(scope, source) }.to raise_error( + /Illegal Resource Type expression, expected result to be a type name, or untitled Resource.*line 1:2/) end it 'for non r-value producing <| |>' do @@ -1058,6 +1245,39 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do end + context 'does not leak variables' do + it 'local variables are gone when lambda ends' do + source = <<-SOURCE + [1,2,3].each |$x| { $y = $x} + $a = $y + SOURCE + expect do + parser.evaluate_string(scope, source) + end.to raise_error(/Unknown variable: 'y'/) + end + + it 'lambda parameters are gone when lambda ends' do + source = <<-SOURCE + [1,2,3].each |$x| { $y = $x} + $a = $x + SOURCE + expect do + parser.evaluate_string(scope, source) + end.to raise_error(/Unknown variable: 'x'/) + end + + it 'does not leak match variables' do + source = <<-SOURCE + if 'xyz' =~ /(x)(y)(z)/ { notice $2 } + case 'abc' { + /(a)(b)(c)/ : { $x = $2 } + } + "-$x-$2-" + SOURCE + expect(parser.evaluate_string(scope, source)).to eq('-b--') + end + end + matcher :have_relationship do |expected| calc = Puppet::Pops::Types::TypeCalculator.new diff --git a/spec/unit/pops/evaluator/logical_ops_spec.rb b/spec/unit/pops/evaluator/logical_ops_spec.rb index e5cdd1f93..d6a179e03 100644 --- a/spec/unit/pops/evaluator/logical_ops_spec.rb +++ b/spec/unit/pops/evaluator/logical_ops_spec.rb @@ -63,8 +63,8 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do evaluate(literal('x').not()).should == false end - it "'' == false" do - evaluate(literal('').not()).should == true + it "'' == true" do + evaluate(literal('').not()).should == false end it ":undef == false" do diff --git a/spec/unit/pops/evaluator/variables_spec.rb b/spec/unit/pops/evaluator/variables_spec.rb index fe93842c4..6c1e9f821 100644 --- a/spec/unit/pops/evaluator/variables_spec.rb +++ b/spec/unit/pops/evaluator/variables_spec.rb @@ -49,111 +49,6 @@ describe 'Puppet::Pops::Impl::EvaluatorImpl' do expect { evaluate_l(block(var('a').set(10), var('a').set(20))) }.to raise_error(/Cannot reassign variable a/) end - context "-= operations" do - # Also see collections_ops_spec.rb where delete via - is fully tested, here only the - # the -= operation itself is tested (there are many combinations) - # - it 'deleting from non existing value produces :undef, nil -= ?' do - top_scope_block = var('b').set([1,2,3]) - local_scope_block = block(var('a').minus_set([4]), fqn('a').var) - evaluate_l(top_scope_block, local_scope_block).should == :undef - end - - it 'deletes from a list' do - top_scope_block = var('a').set([1,2,3]) - local_scope_block = block(var('a').minus_set([2]), fqn('a').var()) - evaluate_l(top_scope_block, local_scope_block).should == [1,3] - end - - it 'deletes from a hash' do - top_scope_block = var('a').set({'a'=>1,'b'=>2,'c'=>3}) - local_scope_block = block(var('a').minus_set('b'), fqn('a').var()) - evaluate_l(top_scope_block, local_scope_block).should == {'a'=>1,'c'=>3} - end - end - - context "+= operations" do - # Also see collections_ops_spec.rb where concatenation via + is fully tested - it "appending to non existing value, nil += []" do - top_scope_block = var('b').set([1,2,3]) - local_scope_block = var('a').plus_set([4]) - evaluate_l(top_scope_block, local_scope_block).should == [4] - end - - context "appending to list" do - it "from list, [] += []" do - top_scope_block = var('a').set([1,2,3]) - local_scope_block = block(var('a').plus_set([4]), fqn('a').var()) - evaluate_l(top_scope_block, local_scope_block).should == [1,2,3,4] - end - - it "from hash, [] += {a=>b}" do - top_scope_block = var('a').set([1,2,3]) - local_scope_block = block(var('a').plus_set({'a' => 1, 'b'=>2}), fqn('a').var()) - evaluate_l(top_scope_block, local_scope_block).should satisfy {|result| - # hash in 1.8.7 is not insertion order preserving, hence this hoop - result == [1,2,3,['a',1],['b',2]] || result == [1,2,3,['b',2],['a',1]] - } - end - - it "from single value, [] += x" do - top_scope_block = var('a').set([1,2,3]) - local_scope_block = block(var('a').plus_set(4), fqn('a').var()) - evaluate_l(top_scope_block, local_scope_block).should == [1,2,3,4] - end - - it "from embedded list, [] += [[x]]" do - top_scope_block = var('a').set([1,2,3]) - local_scope_block = block(var('a').plus_set([[4,5]]), fqn('a').var()) - evaluate_l(top_scope_block, local_scope_block).should == [1,2,3,[4,5]] - end - end - - context "appending to hash" do - it "from hash, {a=>b} += {x=>y}" do - top_scope_block = var('a').set({'a' => 1, 'b' => 2}) - local_scope_block = block(var('a').plus_set({'c' => 3}), fqn('a').var()) - evaluate_l(top_scope_block, local_scope_block) do |scope| - # Assert no change to top scope hash - scope['a'].should == {'a' =>1, 'b'=> 2} - end.should == {'a' => 1, 'b' => 2, 'c' => 3} - end - - it "from list, {a=>b} += ['x', y]" do - top_scope_block = var('a').set({'a' => 1, 'b' => 2}) - local_scope_block = block(var('a').plus_set(['c', 3]), fqn('a').var()) - evaluate_l(top_scope_block, local_scope_block) do |scope| - # Assert no change to top scope hash - scope['a'].should == {'a' =>1, 'b'=> 2} - end.should == {'a' => 1, 'b' => 2, 'c' => 3} - end - - it "with overwrite from hash, {a=>b} += {a=>c}" do - top_scope_block = var('a').set({'a' => 1, 'b' => 2}) - local_scope_block = block(var('a').plus_set({'b' => 4, 'c' => 3}),fqn('a').var()) - evaluate_l(top_scope_block, local_scope_block) do |scope| - # Assert no change to top scope hash - scope['a'].should == {'a' =>1, 'b'=> 2} - end.should == {'a' => 1, 'b' => 4, 'c' => 3} - end - - it "with overwrite from list, {a=>b} += ['a', c]" do - top_scope_block = var('a').set({'a' => 1, 'b' => 2}) - local_scope_block = block(var('a').plus_set(['b', 4, 'c', 3]), fqn('a').var()) - evaluate_l(top_scope_block, local_scope_block) do |scope| - # Assert no change to topscope hash - scope['a'].should == {'a' =>1, 'b'=> 2} - end.should == {'a' => 1, 'b' => 4, 'c' => 3} - end - - it "from odd length array - error" do - top_scope_block = var('a').set({'a' => 1, 'b' => 2}) - local_scope_block = var('a').plus_set(['b', 4, 'c']) - expect { evaluate_l(top_scope_block, local_scope_block) }.to raise_error(/Append assignment \+= failed with error: odd number of arguments for Hash/) - end - end - end - context "access to numeric variables" do it "without a match" do evaluate_l(block(literal(2) + literal(2), diff --git a/spec/unit/pops/issues_spec.rb b/spec/unit/pops/issues_spec.rb index 93f093d61..82dce1b44 100644 --- a/spec/unit/pops/issues_spec.rb +++ b/spec/unit/pops/issues_spec.rb @@ -23,4 +23,174 @@ describe "Puppet::Pops::Issues" do x = Puppet::Pops::Issues::NOT_TOP_LEVEL x.format().should == "Classes, definitions, and nodes may only appear at toplevel or inside other classes" end + +end + +describe "Puppet::Pops::IssueReporter" do + + let(:acceptor) { Puppet::Pops::Validation::Acceptor.new } + + def fake_positioned(number) + stub("positioned_#{number}", :line => number, :pos => number) + end + + def diagnostic(severity, number) + Puppet::Pops::Validation::Diagnostic.new( + severity, + Puppet::Pops::Issues::Issue.new(number) { "#{severity}#{number}" }, + "#{severity}file", + fake_positioned(number)) + end + + def warning(number) + diagnostic(:warning, number) + end + + def deprecation(number) + diagnostic(:deprecation, number) + end + + def error(number) + diagnostic(:error, number) + end + + context "given warnings" do + + before(:each) do + acceptor.accept( warning(1) ) + acceptor.accept( deprecation(1) ) + end + + it "emits warnings if told to emit them" do + Puppet.expects(:warning).twice.with(regexp_matches(/warning1|deprecation1/)) + Puppet::Pops::IssueReporter.assert_and_report(acceptor, { :emit_warnings => true }) + end + + it "does not emit warnings if not told to emit them" do + Puppet.expects(:warning).never + Puppet::Pops::IssueReporter.assert_and_report(acceptor, {}) + end + + it "emits no warnings if :max_warnings is 0" do + acceptor.accept( warning(2) ) + Puppet[:max_warnings] = 0 + Puppet.expects(:warning).once.with(regexp_matches(/deprecation1/)) + Puppet::Pops::IssueReporter.assert_and_report(acceptor, { :emit_warnings => true }) + end + + it "emits no more than 1 warning if :max_warnings is 1" do + acceptor.accept( warning(2) ) + acceptor.accept( warning(3) ) + Puppet[:max_warnings] = 1 + Puppet.expects(:warning).twice.with(regexp_matches(/warning1|deprecation1/)) + Puppet::Pops::IssueReporter.assert_and_report(acceptor, { :emit_warnings => true }) + end + + it "does not emit more deprecations warnings than the max deprecation warnings" do + acceptor.accept( deprecation(2) ) + Puppet[:max_deprecations] = 0 + Puppet.expects(:warning).once.with(regexp_matches(/warning1/)) + Puppet::Pops::IssueReporter.assert_and_report(acceptor, { :emit_warnings => true }) + end + + it "does not emit deprecation warnings, but does emit regular warnings if disable_warnings includes deprecations" do + Puppet[:disable_warnings] = 'deprecations' + Puppet.expects(:warning).once.with(regexp_matches(/warning1/)) + Puppet::Pops::IssueReporter.assert_and_report(acceptor, { :emit_warnings => true }) + end + end + + context "given errors" do + it "logs nothing, but raises the given :message if :emit_errors is repressing error logging" do + acceptor.accept( error(1) ) + Puppet.expects(:err).never + expect do + Puppet::Pops::IssueReporter.assert_and_report(acceptor, { :emit_errors => false, :message => 'special'}) + end.to raise_error(Puppet::ParseError, 'special') + end + + it "prefixes :message if a single error is raised" do + acceptor.accept( error(1) ) + Puppet.expects(:err).never + expect do + Puppet::Pops::IssueReporter.assert_and_report(acceptor, { :message => 'special'}) + end.to raise_error(Puppet::ParseError, /special error1/) + end + + it "logs nothing and raises immediately if there is only one error" do + acceptor.accept( error(1) ) + Puppet.expects(:err).never + expect do + Puppet::Pops::IssueReporter.assert_and_report(acceptor, { }) + end.to raise_error(Puppet::ParseError, /error1/) + end + + it "logs nothing and raises immediately if there are multiple errors but max_errors is 0" do + acceptor.accept( error(1) ) + acceptor.accept( error(2) ) + Puppet[:max_errors] = 0 + Puppet.expects(:err).never + expect do + Puppet::Pops::IssueReporter.assert_and_report(acceptor, { }) + end.to raise_error(Puppet::ParseError, /error1/) + end + + it "logs the :message if there is more than one allowed error" do + acceptor.accept( error(1) ) + acceptor.accept( error(2) ) + Puppet.expects(:err).times(3).with(regexp_matches(/error1|error2|special/)) + expect do + Puppet::Pops::IssueReporter.assert_and_report(acceptor, { :message => 'special'}) + end.to raise_error(Puppet::ParseError, /Giving up/) + end + + it "emits accumulated errors before raising a 'giving up' message if there are more errors than allowed" do + acceptor.accept( error(1) ) + acceptor.accept( error(2) ) + acceptor.accept( error(3) ) + Puppet[:max_errors] = 2 + Puppet.expects(:err).times(2).with(regexp_matches(/error1|error2/)) + expect do + Puppet::Pops::IssueReporter.assert_and_report(acceptor, { }) + end.to raise_error(Puppet::ParseError, /3 errors.*Giving up/) + end + + it "emits accumulated errors before raising a 'giving up' message if there are multiple errors but fewer than limits" do + acceptor.accept( error(1) ) + acceptor.accept( error(2) ) + acceptor.accept( error(3) ) + Puppet[:max_errors] = 4 + Puppet.expects(:err).times(3).with(regexp_matches(/error[123]/)) + expect do + Puppet::Pops::IssueReporter.assert_and_report(acceptor, { }) + end.to raise_error(Puppet::ParseError, /3 errors.*Giving up/) + end + + it "emits errors regardless of disable_warnings setting" do + acceptor.accept( error(1) ) + acceptor.accept( error(2) ) + Puppet[:disable_warnings] = 'deprecations' + Puppet.expects(:err).times(2).with(regexp_matches(/error1|error2/)) + expect do + Puppet::Pops::IssueReporter.assert_and_report(acceptor, { }) + end.to raise_error(Puppet::ParseError, /Giving up/) + end + end + + context "given both" do + + it "logs warnings and errors" do + acceptor.accept( warning(1) ) + acceptor.accept( error(1) ) + acceptor.accept( error(2) ) + acceptor.accept( error(3) ) + acceptor.accept( deprecation(1) ) + Puppet[:max_errors] = 2 + Puppet.expects(:warning).twice.with(regexp_matches(/warning1|deprecation1/)) + Puppet.expects(:err).times(2).with(regexp_matches(/error[123]/)) + expect do + Puppet::Pops::IssueReporter.assert_and_report(acceptor, { :emit_warnings => true }) + end.to raise_error(Puppet::ParseError, /3 errors.*2 warnings.*Giving up/) + end + end end diff --git a/spec/unit/pops/loaders/dependency_loader_spec.rb b/spec/unit/pops/loaders/dependency_loader_spec.rb index dbea5b208..cbdefe897 100644 --- a/spec/unit/pops/loaders/dependency_loader_spec.rb +++ b/spec/unit/pops/loaders/dependency_loader_spec.rb @@ -36,6 +36,23 @@ describe 'dependency loader' do expect(function.class.name).to eq('testmodule::foo') expect(function.is_a?(Puppet::Functions::Function)).to eq(true) end + + it 'can load something in a qualified name space more than once' do + module_dir = dir_containing('testmodule', { + 'lib' => { 'puppet' => { 'functions' => { 'testmodule' => { + 'foo.rb' => 'Puppet::Functions.create_function("testmodule::foo") { def foo; end; }' + }}}}}) + module_loader = Puppet::Pops::Loader::ModuleLoaders::FileBased.new(static_loader, loaders, 'testmodule', module_dir, 'test1') + dep_loader = Puppet::Pops::Loader::DependencyLoader.new(static_loader, 'test-dep', [module_loader]) + + function = dep_loader.load_typed(typed_name(:function, 'testmodule::foo')).value + expect(function.class.name).to eq('testmodule::foo') + expect(function.is_a?(Puppet::Functions::Function)).to eq(true) + + function = dep_loader.load_typed(typed_name(:function, 'testmodule::foo')).value + expect(function.class.name).to eq('testmodule::foo') + expect(function.is_a?(Puppet::Functions::Function)).to eq(true) + end end def typed_name(type, name) diff --git a/spec/unit/pops/loaders/loader_paths_spec.rb b/spec/unit/pops/loaders/loader_paths_spec.rb index 2929fe7f8..2cd565a8c 100644 --- a/spec/unit/pops/loaders/loader_paths_spec.rb +++ b/spec/unit/pops/loaders/loader_paths_spec.rb @@ -5,7 +5,6 @@ require 'puppet/loaders' describe 'loader paths' do include PuppetSpec::Files - before(:each) { Puppet[:biff] = true } let(:static_loader) { Puppet::Pops::Loader::StaticLoader.new() } let(:unused_loaders) { nil } @@ -17,17 +16,13 @@ describe 'loader paths' do 'lib' => { 'puppet' => { 'functions' => {}, - 'parser' => { - 'functions' => {}, - } }}}) module_loader = Puppet::Pops::Loader::ModuleLoaders::FileBased.new(static_loader, unused_loaders, 'testmodule', module_dir, 'test1') effective_paths = Puppet::Pops::Loader::LoaderPaths.relative_paths_for_type(:function, module_loader) expect(effective_paths.collect(&:generic_path)).to eq([ - File.join(module_dir, 'lib', 'puppet', 'functions'), # 4x functions - File.join(module_dir, 'lib', 'puppet','parser', 'functions') # 3x functions + File.join(module_dir, 'lib', 'puppet', 'functions') ]) end @@ -39,8 +34,6 @@ describe 'loader paths' do expect(effective_paths.size).to be_eql(1) expect(effective_paths[0].generic_path).to be_eql(File.join(module_dir, 'lib', 'puppet', 'functions')) - expect(module_loader.path_index.size).to be_eql(1) - expect(module_loader.path_index.include?(File.join(module_dir, 'lib', 'puppet', 'functions', 'foo.rb'))).to be(true) end it 'all function smart-paths produces entries if they exist' do @@ -48,19 +41,15 @@ describe 'loader paths' do 'lib' => { 'puppet' => { 'functions' => {'foo4x.rb' => 'ignored in this test'}, - 'parser' => { - 'functions' => {'foo3x.rb' => 'ignored in this test'}, - } }}}) module_loader = Puppet::Pops::Loader::ModuleLoaders::FileBased.new(static_loader, unused_loaders, 'testmodule', module_dir, 'test1') effective_paths = module_loader.smart_paths.effective_paths(:function) - expect(effective_paths.size).to eq(2) - expect(module_loader.path_index.size).to eq(2) + expect(effective_paths.size).to eq(1) + expect(module_loader.path_index.size).to eq(1) path_index = module_loader.path_index - expect(path_index.include?(File.join(module_dir, 'lib', 'puppet', 'functions', 'foo4x.rb'))).to eq(true) - expect(path_index.include?(File.join(module_dir, 'lib', 'puppet', 'parser', 'functions', 'foo3x.rb'))).to eq(true) + expect(path_index).to include(File.join(module_dir, 'lib', 'puppet', 'functions', 'foo4x.rb')) end end end diff --git a/spec/unit/pops/loaders/loaders_spec.rb b/spec/unit/pops/loaders/loaders_spec.rb index daa9c716c..831236698 100644 --- a/spec/unit/pops/loaders/loaders_spec.rb +++ b/spec/unit/pops/loaders/loaders_spec.rb @@ -4,6 +4,25 @@ require 'puppet_spec/files' require 'puppet/pops' require 'puppet/loaders' +describe 'loader helper classes' do + it 'NamedEntry holds values and is frozen' do + ne = Puppet::Pops::Loader::Loader::NamedEntry.new('name', 'value', 'origin') + expect(ne.frozen?).to be_true + expect(ne.typed_name).to eql('name') + expect(ne.origin).to eq('origin') + expect(ne.value).to eq('value') + end + + it 'TypedName holds values and is frozen' do + tn = Puppet::Pops::Loader::Loader::TypedName.new(:function, '::foo::bar') + expect(tn.frozen?).to be_true + expect(tn.type).to eq(:function) + expect(tn.name_parts).to eq(['foo', 'bar']) + expect(tn.name).to eq('foo::bar') + expect(tn.qualified).to be_true + end +end + describe 'loaders' do include PuppetSpec::Files @@ -29,17 +48,6 @@ describe 'loaders' do expect(loaders.private_environment_loader().to_s).to eql("(DependencyLoader 'environment' [])") end - it 'can load 3x system functions' do - Puppet[:biff] = true - loaders = Puppet::Pops::Loaders.new(empty_test_env) - puppet_loader = loaders.puppet_system_loader() - - function = puppet_loader.load_typed(typed_name(:function, 'sprintf')).value - - expect(function.class.name).to eq('sprintf') - expect(function).to be_a(Puppet::Functions::Function) - end - it 'can load a function using a qualified or unqualified name from a module with metadata' do loaders = Puppet::Pops::Loaders.new(environment_for(module_with_metadata)) modulea_loader = loaders.public_loader_for_module('modulea') @@ -91,6 +99,18 @@ describe 'loaders' do expect(function.call({})).to eql("usee::callee() was told 'passed value' + I am user::caller()") end + it 'can load a function more than once from modules' do + env = environment_for(dependent_modules_with_metadata) + loaders = Puppet::Pops::Loaders.new(env) + + moduleb_loader = loaders.private_loader_for_module('user') + function = moduleb_loader.load_typed(typed_name(:function, 'user::caller')).value + expect(function.call({})).to eql("usee::callee() was told 'passed value' + I am user::caller()") + + function = moduleb_loader.load_typed(typed_name(:function, 'user::caller')).value + expect(function.call({})).to eql("usee::callee() was told 'passed value' + I am user::caller()") + end + def environment_for(*module_paths) Puppet::Node::Environment.create(:'*test*', module_paths, '') end diff --git a/spec/unit/pops/loaders/module_loaders_spec.rb b/spec/unit/pops/loaders/module_loaders_spec.rb index 9d0c1a86d..058e765de 100644 --- a/spec/unit/pops/loaders/module_loaders_spec.rb +++ b/spec/unit/pops/loaders/module_loaders_spec.rb @@ -84,35 +84,6 @@ describe 'FileBased module loader' do expect(function.is_a?(Puppet::Functions::Function)).to eq(true) end - context 'when delegating 3x to 4x' do - before(:each) { Puppet[:biff] = true } - - it 'can load a 3x function API ruby function in global name space' do - module_dir = dir_containing('testmodule', { - 'lib' => { - 'puppet' => { - 'parser' => { - 'functions' => { - 'foo3x.rb' => <<-CODE - Puppet::Parser::Functions::newfunction( - :foo3x, :type => :rvalue, - :arity => 1 - ) do |args| - args[0] - end - CODE - } - } - } - }}) - - module_loader = Puppet::Pops::Loader::ModuleLoaders::FileBased.new(static_loader, loaders, 'testmodule', module_dir, 'test1') - function = module_loader.load_typed(typed_name(:function, 'foo3x')).value - expect(function.class.name).to eq('foo3x') - expect(function.is_a?(Puppet::Functions::Function)).to eq(true) - end - end - def typed_name(type, name) Puppet::Pops::Loader::Loader::TypedName.new(type, name) end diff --git a/spec/unit/pops/loaders/static_loader_spec.rb b/spec/unit/pops/loaders/static_loader_spec.rb index e1a73273e..3d85f4522 100644 --- a/spec/unit/pops/loaders/static_loader_spec.rb +++ b/spec/unit/pops/loaders/static_loader_spec.rb @@ -37,6 +37,12 @@ describe 'the static loader' do it "uses the evaluator to format output" do expect(loader.load(:function, level).call({}, ['yay', 'surprise']).to_s).to eql('[yay, surprise]') end + + it 'outputs name of source (scope) by passing it to the Log utility' do + the_scope = {} + Puppet::Util::Log.any_instance.expects(:source=).with(the_scope) + loader.load(:function, level).call(the_scope, 'x') + end end end diff --git a/spec/unit/pops/parser/epp_parser_spec.rb b/spec/unit/pops/parser/epp_parser_spec.rb index 0db4ba7d9..fb32b9ba4 100644 --- a/spec/unit/pops/parser/epp_parser_spec.rb +++ b/spec/unit/pops/parser/epp_parser_spec.rb @@ -51,36 +51,65 @@ describe "epp parser" do context "handles parsing of" do it "text (and nothing else)" do - dump(parse("Hello World")).should == "(lambda (epp (block (render-s 'Hello World'))))" + dump(parse("Hello World")).should == [ + "(lambda (epp (block", + " (render-s 'Hello World')", + ")))"].join("\n") end it "template parameters" do - dump(parse("<%|$x|%>Hello World")).should == "(lambda (parameters x) (epp (block (render-s 'Hello World'))))" + dump(parse("<%|$x|%>Hello World")).should == [ + "(lambda (parameters x) (epp (block", + " (render-s 'Hello World')", + ")))"].join("\n") end it "template parameters with default" do - dump(parse("<%|$x='cigar'|%>Hello World")).should == "(lambda (parameters (= x 'cigar')) (epp (block (render-s 'Hello World'))))" + dump(parse("<%|$x='cigar'|%>Hello World")).should == [ + "(lambda (parameters (= x 'cigar')) (epp (block", + " (render-s 'Hello World')", + ")))"].join("\n") end it "template parameters with and without default" do - dump(parse("<%|$x='cigar', $y|%>Hello World")).should == "(lambda (parameters (= x 'cigar') y) (epp (block (render-s 'Hello World'))))" + dump(parse("<%|$x='cigar', $y|%>Hello World")).should == [ + "(lambda (parameters (= x 'cigar') y) (epp (block", + " (render-s 'Hello World')", + ")))"].join("\n") end it "template parameters + additional setup" do - dump(parse("<%|$x| $y = 10 %>Hello World")).should == "(lambda (parameters x) (epp (block (= $y 10) (render-s 'Hello World'))))" + dump(parse("<%|$x| $y = 10 %>Hello World")).should == [ + "(lambda (parameters x) (epp (block", + " (= $y 10)", + " (render-s 'Hello World')", + ")))"].join("\n") end it "comments" do - dump(parse("<%#($x='cigar', $y)%>Hello World")).should == "(lambda (epp (block (render-s 'Hello World'))))" + dump(parse("<%#($x='cigar', $y)%>Hello World")).should == [ + "(lambda (epp (block", + " (render-s 'Hello World')", + ")))" + ].join("\n") end it "verbatim epp tags" do - dump(parse("<%% contemplating %%>Hello World")).should == "(lambda (epp (block (render-s '<% contemplating %>Hello World'))))" + dump(parse("<%% contemplating %%>Hello World")).should == [ + "(lambda (epp (block", + " (render-s '<% contemplating %>Hello World')", + ")))" + ].join("\n") end it "expressions" do - dump(parse("We all live in <%= 3.14 - 2.14 %> world")).should == - "(lambda (epp (block (render-s 'We all live in ') (render (- 3.14 2.14)) (render-s ' world'))))" + dump(parse("We all live in <%= 3.14 - 2.14 %> world")).should == [ + "(lambda (epp (block", + " (render-s 'We all live in ')", + " (render (- 3.14 2.14))", + " (render-s ' world')", + ")))" + ].join("\n") end end end diff --git a/spec/unit/pops/parser/evaluating_parser_spec.rb b/spec/unit/pops/parser/evaluating_parser_spec.rb index 8448a5af3..7645b9bc2 100644 --- a/spec/unit/pops/parser/evaluating_parser_spec.rb +++ b/spec/unit/pops/parser/evaluating_parser_spec.rb @@ -9,7 +9,6 @@ describe 'The Evaluating Parser' do include PuppetSpec::Scope let(:acceptor) { Puppet::Pops::Validation::Acceptor.new() } - let(:diag) { Puppet::Pops::Binder::Hiera2::DiagnosticProducer.new(acceptor) } let(:scope) { s = create_test_scope_for_node(node); s } let(:node) { 'node.example.com' } diff --git a/spec/unit/pops/parser/lexer2_spec.rb b/spec/unit/pops/parser/lexer2_spec.rb index 8ccdc2630..b9a0a916b 100644 --- a/spec/unit/pops/parser/lexer2_spec.rb +++ b/spec/unit/pops/parser/lexer2_spec.rb @@ -21,7 +21,7 @@ describe 'Lexer2' do include EgrammarLexer2Spec { - :LBRACK => '[', + :LISTSTART => '[', :RBRACK => ']', :LBRACE => '{', :RBRACE => '}', @@ -69,6 +69,10 @@ describe 'Lexer2' do end end + it "should lex [ in position after non whitespace as LBRACK" do + tokens_scanned_from("a[").should match_tokens2(:NAME, :LBRACK) + end + { "case" => :CASE, "class" => :CLASS, @@ -186,6 +190,16 @@ describe 'Lexer2' do end end + { "''" => [2, ""], + "'a'" => [3, "a"], + "'a\\'b'" => [6, "a'b"], + }.each do |source, expected| + it "should lex a single quoted STRING on the form #{source} as having length #{expected[0]}" do + length, value = expected + tokens_scanned_from(source).should match_tokens2([:STRING, value, {:line => 1, :pos=>1, :length=> length}]) + end + end + { '""' => '', '"a"' => 'a', '"a\'b"' => "a'b", @@ -229,7 +243,7 @@ describe 'Lexer2' do it "differentiates between foo[x] and foo [x] (whitespace)" do tokens_scanned_from("$a[1]").should match_tokens2(:VARIABLE, :LBRACK, :NUMBER, :RBRACK) - tokens_scanned_from("$a [1]").should match_tokens2(:VARIABLE, :LBRACK, :NUMBER, :RBRACK) + tokens_scanned_from("$a [1]").should match_tokens2(:VARIABLE, :LISTSTART, :NUMBER, :RBRACK) tokens_scanned_from("a[1]").should match_tokens2(:NAME, :LBRACK, :NUMBER, :RBRACK) tokens_scanned_from("a [1]").should match_tokens2(:NAME, :LISTSTART, :NUMBER, :RBRACK) tokens_scanned_from(" if \n\r\t\nif if ").should match_tokens2(:IF, :IF, :IF) @@ -257,7 +271,9 @@ describe 'Lexer2' do "!~" => [:NOMATCH, "!~ /./"], "," => [:COMMA, ", /./"], "(" => [:LPAREN, "( /./"], - "[" => [:LBRACK, "[ /./"], + "[" => [:LISTSTART, "[ /./"], + "[" => [[:NAME, :LBRACK], "a[ /./"], + "[" => [[:NAME, :LISTSTART], "a [ /./"], "{" => [:LBRACE, "{ /./"], "+" => [:PLUS, "+ /./"], "-" => [:MINUS, "- /./"], @@ -265,7 +281,8 @@ describe 'Lexer2' do ";" => [:SEMIC, "; /./"], }.each do |token, entry| it "should lex regexp after '#{token}'" do - tokens_scanned_from(entry[1]).should match_tokens2(entry[0], :REGEX) + expected = [entry[0], :REGEX].flatten + tokens_scanned_from(entry[1]).should match_tokens2(*expected) end end diff --git a/spec/unit/pops/parser/lexer_spec.rb b/spec/unit/pops/parser/lexer_spec.rb deleted file mode 100755 index 40d9b3e51..000000000 --- a/spec/unit/pops/parser/lexer_spec.rb +++ /dev/null @@ -1,840 +0,0 @@ -#! /usr/bin/env ruby -require 'spec_helper' - -require 'puppet/pops' - -# This is a special matcher to match easily lexer output -RSpec::Matchers.define :be_like do |*expected| - match do |actual| - diffable - expected.zip(actual).all? { |e,a| !e or a[0] == e or (e.is_a? Array and a[0] == e[0] and (a[1] == e[1] or (a[1].is_a?(Hash) and a[1][:value] == e[1]))) } - end -end -__ = nil - -module EgrammarLexerSpec - def self.tokens_scanned_from(s) - lexer = Puppet::Pops::Parser::Lexer.new - lexer.string = s - tokens = lexer.fullscan[0..-2] - tokens.map do |t| - key = t[0] - options = t[1] - if options[:locator] - # unresolved locations needs to be resolved for tests that check positioning - [key, - options[:locator].to_location_hash( - options[:offset], - options[:end_offset]).merge({:value => options[:value]}) ] - else - t - end - end - end -end - -describe Puppet::Pops::Parser::Lexer do - include EgrammarLexerSpec - - describe "when reading strings" do - before { @lexer = Puppet::Pops::Parser::Lexer.new } - - it "should increment the line count for every carriage return in the string" do - @lexer.string = "'this\nis\natest'" - @lexer.fullscan[0..-2] - - line = @lexer.line - line.should == 3 - end - - it "should not increment the line count for escapes in the string" do - @lexer.string = "'this\\nis\\natest'" - @lexer.fullscan[0..-2] - - @lexer.line.should == 1 - end - - it "should not think the terminator is escaped, when preceeded by an even number of backslashes" do - @lexer.string = "'here\nis\nthe\nstring\\\\'with\nextra\njunk" - @lexer.fullscan[0..-2] - - @lexer.line.should == 6 - end - - { - 'r' => "\r", - 'n' => "\n", - 't' => "\t", - 's' => " " - }.each do |esc, expected_result| - it "should recognize \\#{esc} sequence" do - @lexer.string = "\\#{esc}'" - @lexer.slurpstring("'")[0].should == expected_result - end - end - end -end - -describe Puppet::Pops::Parser::Lexer::Token, "when initializing" do - it "should create a regex if the first argument is a string" do - Puppet::Pops::Parser::Lexer::Token.new("something", :NAME).regex.should == %r{something} - end - - it "should set the string if the first argument is one" do - Puppet::Pops::Parser::Lexer::Token.new("something", :NAME).string.should == "something" - end - - it "should set the regex if the first argument is one" do - Puppet::Pops::Parser::Lexer::Token.new(%r{something}, :NAME).regex.should == %r{something} - end -end - -describe Puppet::Pops::Parser::Lexer::TokenList do - before do - @list = Puppet::Pops::Parser::Lexer::TokenList.new - end - - it "should have a method for retrieving tokens by the name" do - token = @list.add_token :name, "whatever" - @list[:name].should equal(token) - end - - it "should have a method for retrieving string tokens by the string" do - token = @list.add_token :name, "whatever" - @list.lookup("whatever").should equal(token) - end - - it "should add tokens to the list when directed" do - token = @list.add_token :name, "whatever" - @list[:name].should equal(token) - end - - it "should have a method for adding multiple tokens at once" do - @list.add_tokens "whatever" => :name, "foo" => :bar - @list[:name].should_not be_nil - @list[:bar].should_not be_nil - end - - it "should fail to add tokens sharing a name with an existing token" do - @list.add_token :name, "whatever" - expect { @list.add_token :name, "whatever" }.to raise_error(ArgumentError) - end - - it "should set provided options on tokens being added" do - token = @list.add_token :name, "whatever", :skip_text => true - token.skip_text.should == true - end - - it "should define any provided blocks as a :convert method" do - token = @list.add_token(:name, "whatever") do "foo" end - token.convert.should == "foo" - end - - it "should store all string tokens in the :string_tokens list" do - one = @list.add_token(:name, "1") - @list.string_tokens.should be_include(one) - end - - it "should store all regex tokens in the :regex_tokens list" do - one = @list.add_token(:name, %r{one}) - @list.regex_tokens.should be_include(one) - end - - it "should not store string tokens in the :regex_tokens list" do - one = @list.add_token(:name, "1") - @list.regex_tokens.should_not be_include(one) - end - - it "should not store regex tokens in the :string_tokens list" do - one = @list.add_token(:name, %r{one}) - @list.string_tokens.should_not be_include(one) - end - - it "should sort the string tokens inversely by length when asked" do - one = @list.add_token(:name, "1") - two = @list.add_token(:other, "12") - @list.sort_tokens - @list.string_tokens.should == [two, one] - end -end - -describe Puppet::Pops::Parser::Lexer::TOKENS do - before do - @lexer = Puppet::Pops::Parser::Lexer.new - end - - { - :LBRACK => '[', - :RBRACK => ']', -# :LBRACE => '{', -# :RBRACE => '}', - :LPAREN => '(', - :RPAREN => ')', - :EQUALS => '=', - :ISEQUAL => '==', - :GREATEREQUAL => '>=', - :GREATERTHAN => '>', - :LESSTHAN => '<', - :LESSEQUAL => '<=', - :NOTEQUAL => '!=', - :NOT => '!', - :COMMA => ',', - :DOT => '.', - :COLON => ':', - :AT => '@', - :LLCOLLECT => '<<|', - :RRCOLLECT => '|>>', - :LCOLLECT => '<|', - :RCOLLECT => '|>', - :SEMIC => ';', - :QMARK => '?', - :BACKSLASH => '\\', - :FARROW => '=>', - :PARROW => '+>', - :APPENDS => '+=', - :DELETES => '-=', - :PLUS => '+', - :MINUS => '-', - :DIV => '/', - :TIMES => '*', - :LSHIFT => '<<', - :RSHIFT => '>>', - :MATCH => '=~', - :NOMATCH => '!~', - :IN_EDGE => '->', - :OUT_EDGE => '<-', - :IN_EDGE_SUB => '~>', - :OUT_EDGE_SUB => '<~', - :PIPE => '|', - }.each do |name, string| - it "should have a token named #{name.to_s}" do - Puppet::Pops::Parser::Lexer::TOKENS[name].should_not be_nil - end - - it "should match '#{string}' for the token #{name.to_s}" do - Puppet::Pops::Parser::Lexer::TOKENS[name].string.should == string - end - end - - { - "case" => :CASE, - "class" => :CLASS, - "default" => :DEFAULT, - "define" => :DEFINE, -# "import" => :IMPORT, # done as a function in egrammar - "if" => :IF, - "elsif" => :ELSIF, - "else" => :ELSE, - "inherits" => :INHERITS, - "node" => :NODE, - "and" => :AND, - "or" => :OR, - "undef" => :UNDEF, - "false" => :FALSE, - "true" => :TRUE, - "in" => :IN, - "unless" => :UNLESS, - }.each do |string, name| - it "should have a keyword named #{name.to_s}" do - Puppet::Pops::Parser::Lexer::KEYWORDS[name].should_not be_nil - end - - it "should have the keyword for #{name.to_s} set to #{string}" do - Puppet::Pops::Parser::Lexer::KEYWORDS[name].string.should == string - end - end - - # These tokens' strings don't matter, just that the tokens exist. - [:STRING, :DQPRE, :DQMID, :DQPOST, :BOOLEAN, :NAME, :NUMBER, :COMMENT, :MLCOMMENT, - :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 - end - end -end - -describe Puppet::Pops::Parser::Lexer::TOKENS[:CLASSREF] do - before { @token = Puppet::Pops::Parser::Lexer::TOKENS[:CLASSREF] } - - it "should match against single upper-case alpha-numeric terms" do - @token.regex.should =~ "One" - end - - it "should match against upper-case alpha-numeric terms separated by double colons" do - @token.regex.should =~ "One::Two" - end - - it "should match against many upper-case alpha-numeric terms separated by double colons" do - @token.regex.should =~ "One::Two::Three::Four::Five" - end - - it "should match against upper-case alpha-numeric terms prefixed by double colons" do - @token.regex.should =~ "::One" - end -end - -describe Puppet::Pops::Parser::Lexer::TOKENS[:NAME] do - before { @token = Puppet::Pops::Parser::Lexer::TOKENS[:NAME] } - - it "should match against lower-case alpha-numeric terms" do - @token.regex.should =~ "one-two" - end - - it "should return itself and the value if the matched term is not a keyword" do - Puppet::Pops::Parser::Lexer::KEYWORDS.expects(:lookup).returns(nil) - @token.convert(stub("lexer"), "myval").should == [Puppet::Pops::Parser::Lexer::TOKENS[:NAME], "myval"] - end - - it "should return the keyword token and the value if the matched term is a keyword" do - keyword = stub 'keyword', :name => :testing - Puppet::Pops::Parser::Lexer::KEYWORDS.expects(:lookup).returns(keyword) - @token.convert(stub("lexer"), "myval").should == [keyword, "myval"] - end - - it "should return the BOOLEAN token and 'true' if the matched term is the string 'true'" do - keyword = stub 'keyword', :name => :TRUE - Puppet::Pops::Parser::Lexer::KEYWORDS.expects(:lookup).returns(keyword) - @token.convert(stub('lexer'), "true").should == [Puppet::Pops::Parser::Lexer::TOKENS[:BOOLEAN], true] - end - - it "should return the BOOLEAN token and 'false' if the matched term is the string 'false'" do - keyword = stub 'keyword', :name => :FALSE - Puppet::Pops::Parser::Lexer::KEYWORDS.expects(:lookup).returns(keyword) - @token.convert(stub('lexer'), "false").should == [Puppet::Pops::Parser::Lexer::TOKENS[:BOOLEAN], false] - end - - it "should match against lower-case alpha-numeric terms separated by double colons" do - @token.regex.should =~ "one::two" - end - - it "should match against many lower-case alpha-numeric terms separated by double colons" do - @token.regex.should =~ "one::two::three::four::five" - end - - it "should match against lower-case alpha-numeric terms prefixed by double colons" do - @token.regex.should =~ "::one" - end - - it "should match against nested terms starting with numbers" do - @token.regex.should =~ "::1one::2two::3three" - end -end - -describe Puppet::Pops::Parser::Lexer::TOKENS[:NUMBER] do - before do - @token = Puppet::Pops::Parser::Lexer::TOKENS[:NUMBER] - @regex = @token.regex - end - - it "should match against numeric terms" do - @regex.should =~ "2982383139" - end - - it "should match against float terms" do - @regex.should =~ "29823.235" - end - - it "should match against hexadecimal terms" do - @regex.should =~ "0xBEEF0023" - end - - it "should match against float with exponent terms" do - @regex.should =~ "10e23" - end - - it "should match against float terms with negative exponents" do - @regex.should =~ "10e-23" - end - - it "should match against float terms with fractional parts and exponent" do - @regex.should =~ "1.234e23" - end -end - -describe Puppet::Pops::Parser::Lexer::TOKENS[:COMMENT] do - before { @token = Puppet::Pops::Parser::Lexer::TOKENS[:COMMENT] } - - it "should match against lines starting with '#'" do - @token.regex.should =~ "# this is a comment" - end - - it "should be marked to get skipped" do - @token.skip?.should be_true - end - - it "'s block should return the comment without any text" do - # This is a silly test, the original tested that the comments was processed, but - # all comments are skipped anyway, and never collected for documentation. - # - @token.convert(@lexer,"# this is a comment")[1].should == "" - end -end - -describe Puppet::Pops::Parser::Lexer::TOKENS[:MLCOMMENT] do - before do - @token = Puppet::Pops::Parser::Lexer::TOKENS[:MLCOMMENT] - @lexer = stub 'lexer', :line => 0 - end - - it "should match against lines enclosed with '/*' and '*/'" do - @token.regex.should =~ "/* this is a comment */" - end - - it "should match multiple lines enclosed with '/*' and '*/'" do - @token.regex.should =~ """/* - this is a comment - */""" - end - -# # TODO: REWRITE THIS TEST TO NOT BE BASED ON INTERNALS -# it "should increase the lexer current line number by the amount of lines spanned by the comment" do -# @lexer.expects(:line=).with(2) -# @token.convert(@lexer, "1\n2\n3") -# end - - it "should not greedily match comments" do - match = @token.regex.match("/* first */ word /* second */") - match[1].should == " first " - end - - it "'s block should return the comment without the comment marks" do - # This is a silly test, the original tested that the comments was processed, but - # all comments are skipped anyway, and never collected for documentation. - # - @lexer.stubs(:line=).with(0) - - @token.convert(@lexer,"/* this is a comment */")[1].should == "" - end -end - -describe Puppet::Pops::Parser::Lexer::TOKENS[:RETURN] do - before { @token = Puppet::Pops::Parser::Lexer::TOKENS[:RETURN] } - - it "should match against carriage returns" do - @token.regex.should =~ "\n" - end - - it "should be marked to initiate text skipping" do - @token.skip_text.should be_true - end -end - -shared_examples_for "handling `-` in standard variable names for egrammar" do |prefix| - # Watch out - a regex might match a *prefix* on these, not just the whole - # word, so make sure you don't have false positive or negative results based - # on that. - legal = %w{f foo f::b foo::b f::bar foo::bar 3 foo3 3foo} - illegal = %w{f- f-o -f f::-o f::o- f::o-o} - - ["", "::"].each do |global_scope| - legal.each do |name| - var = prefix + global_scope + name - it "should accept #{var.inspect} as a valid variable name" do - (subject.regex.match(var) || [])[0].should == var - end - end - - illegal.each do |name| - var = prefix + global_scope + name - it "when `variable_with_dash` is disabled it should NOT accept #{var.inspect} as a valid variable name" do - Puppet[:allow_variables_with_dashes] = false - (subject.regex.match(var) || [])[0].should_not == var - end - - it "when `variable_with_dash` is enabled it should NOT accept #{var.inspect} as a valid variable name" do - Puppet[:allow_variables_with_dashes] = true - (subject.regex.match(var) || [])[0].should_not == var - end - end - end -end - -describe Puppet::Pops::Parser::Lexer::TOKENS[:DOLLAR_VAR] do - its(:skip_text) { should be_false } - - it_should_behave_like "handling `-` in standard variable names for egrammar", '$' -end - -describe Puppet::Pops::Parser::Lexer::TOKENS[:VARIABLE] do - its(:skip_text) { should be_false } - - it_should_behave_like "handling `-` in standard variable names for egrammar", '' -end - -describe "the horrible deprecation / compatibility variables with dashes" do - - context "deprecation warnings" do - before :each do Puppet[:allow_variables_with_dashes] = true end - - it "does not warn about a variable without a dash" do - Puppet.expects(:deprecation_warning).never - - EgrammarLexerSpec.tokens_scanned_from('$c').should == [ - [:VARIABLE, {:value=>"c", :line=>1, :pos=>1, :offset=>0, :length=>2}] - ] - end - - it "does not warn about referencing a class name that contains a dash" do - Puppet.expects(:deprecation_warning).never - - EgrammarLexerSpec.tokens_scanned_from('foo-bar').should == [ - [:NAME, {:value=>"foo-bar", :line=>1, :pos=>1, :offset=>0, :length=>7}] - ] - end - end -end - - -describe Puppet::Pops::Parser::Lexer,"when lexing strings" do - { - %q{'single quoted string')} => [[:STRING,'single quoted string']], - %q{"double quoted string"} => [[:STRING,'double quoted string']], - %q{'single quoted string with an escaped "\\'"'} => [[:STRING,'single quoted string with an escaped "\'"']], - %q{'single quoted string with an escaped "\$"'} => [[:STRING,'single quoted string with an escaped "\$"']], - %q{'single quoted string with an escaped "\."'} => [[:STRING,'single quoted string with an escaped "\."']], - %q{'single quoted string with an escaped "\r\n"'} => [[:STRING,'single quoted string with an escaped "\r\n"']], - %q{'single quoted string with an escaped "\n"'} => [[:STRING,'single quoted string with an escaped "\n"']], - %q{'single quoted string with an escaped "\\\\"'} => [[:STRING,'single quoted string with an escaped "\\\\"']], - %q{"string with an escaped '\\"'"} => [[:STRING,"string with an escaped '\"'"]], - %q{"string with an escaped '\\$'"} => [[:STRING,"string with an escaped '$'"]], - %Q{"string with a line ending with a backslash: \\\nfoo"} => [[:STRING,"string with a line ending with a backslash: foo"]], - %q{"string with $v (but no braces)"} => [[:DQPRE,"string with "],[:VARIABLE,'v'],[:DQPOST,' (but no braces)']], - %q["string with ${v} in braces"] => [[:DQPRE,"string with "],[:VARIABLE,'v'],[:DQPOST,' in braces']], - %q["string with ${qualified::var} in braces"] => [[:DQPRE,"string with "],[:VARIABLE,'qualified::var'],[:DQPOST,' in braces']], - %q{"string with $v and $v (but no braces)"} => [[:DQPRE,"string with "],[:VARIABLE,"v"],[:DQMID," and "],[:VARIABLE,"v"],[:DQPOST," (but no braces)"]], - %q["string with ${v} and ${v} in braces"] => [[:DQPRE,"string with "],[:VARIABLE,"v"],[:DQMID," and "],[:VARIABLE,"v"],[:DQPOST," in braces"]], - %q["string with ${'a nested single quoted string'} inside it."] => [[:DQPRE,"string with "],[:STRING,'a nested single quoted string'],[:DQPOST,' inside it.']], - %q["string with ${['an array ',$v2]} in it."] => [[:DQPRE,"string with "],:LBRACK,[:STRING,"an array "],:COMMA,[:VARIABLE,"v2"],:RBRACK,[:DQPOST," in it."]], - %q{a simple "scanner" test} => [[:NAME,"a"],[:NAME,"simple"], [:STRING,"scanner"],[:NAME,"test"]], - %q{a simple 'single quote scanner' test} => [[:NAME,"a"],[:NAME,"simple"], [:STRING,"single quote scanner"],[:NAME,"test"]], - %q{a harder 'a $b \c"'} => [[:NAME,"a"],[:NAME,"harder"], [:STRING,'a $b \c"']], - %q{a harder "scanner test"} => [[:NAME,"a"],[:NAME,"harder"], [:STRING,"scanner test"]], - %q{a hardest "scanner \"test\""} => [[:NAME,"a"],[:NAME,"hardest"],[:STRING,'scanner "test"']], - %Q{a hardestest "scanner \\"test\\"\n"} => [[:NAME,"a"],[:NAME,"hardestest"],[:STRING,%Q{scanner "test"\n}]], - %q{function("call")} => [[:NAME,"function"],[:LPAREN,"("],[:STRING,'call'],[:RPAREN,")"]], - %q["string with ${(3+5)/4} nested math."] => [[:DQPRE,"string with "],:LPAREN,[:NAME,"3"],:PLUS,[:NAME,"5"],:RPAREN,:DIV,[:NAME,"4"],[:DQPOST," nested math."]], - %q["$$$$"] => [[:STRING,"$$$$"]], - %q["$variable"] => [[:DQPRE,""],[:VARIABLE,"variable"],[:DQPOST,""]], - %q["$var$other"] => [[:DQPRE,""],[:VARIABLE,"var"],[:DQMID,""],[:VARIABLE,"other"],[:DQPOST,""]], - %q["foo$bar$"] => [[:DQPRE,"foo"],[:VARIABLE,"bar"],[:DQPOST,"$"]], - %q["foo$$bar"] => [[:DQPRE,"foo$"],[:VARIABLE,"bar"],[:DQPOST,""]], - %q[""] => [[:STRING,""]], - %q["123 456 789 0"] => [[:STRING,"123 456 789 0"]], - %q["${123} 456 $0"] => [[:DQPRE,""],[:VARIABLE,"123"],[:DQMID," 456 "],[:VARIABLE,"0"],[:DQPOST,""]], - %q["$foo::::bar"] => [[:DQPRE,""],[:VARIABLE,"foo"],[:DQPOST,"::::bar"]], - # 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) - end - } -end - -describe Puppet::Pops::Parser::Lexer::TOKENS[:DOLLAR_VAR] do - before { @token = Puppet::Pops::Parser::Lexer::TOKENS[:DOLLAR_VAR] } - - it "should match against alpha words prefixed with '$'" do - @token.regex.should =~ '$this_var' - end - - it "should return the VARIABLE token and the variable name stripped of the '$'" do - @token.convert(stub("lexer"), "$myval").should == [Puppet::Pops::Parser::Lexer::TOKENS[:VARIABLE], "myval"] - end -end - -describe Puppet::Pops::Parser::Lexer::TOKENS[:REGEX] do - before { @token = Puppet::Pops::Parser::Lexer::TOKENS[:REGEX] } - - it "should match against any expression enclosed in //" do - @token.regex.should =~ '/this is a regex/' - end - - it 'should not match if there is \n in the regex' do - @token.regex.should_not =~ "/this is \n a regex/" - end - - describe "when scanning" do - it "should not consider escaped slashes to be the end of a regex" do - EgrammarLexerSpec.tokens_scanned_from("$x =~ /this \\/ foo/").should be_like(__,__,[:REGEX,%r{this / foo}]) - end - - it "should not lex chained division as a regex" do - EgrammarLexerSpec.tokens_scanned_from("$x = $a/$b/$c").collect { |name, data| name }.should_not be_include( :REGEX ) - end - - it "should accept a regular expression after NODE" do - EgrammarLexerSpec.tokens_scanned_from("node /www.*\.mysite\.org/").should be_like(__,[:REGEX,Regexp.new("www.*\.mysite\.org")]) - end - - it "should accept regular expressions in a CASE" do - s = %q{case $variable { - "something": {$othervar = 4096 / 2} - /regex/: {notice("this notably sucks")} - } - } - EgrammarLexerSpec.tokens_scanned_from(s).should be_like( - :CASE,:VARIABLE,:LBRACE,:STRING,:COLON,:LBRACE,:VARIABLE,:EQUALS,:NAME,:DIV,:NAME,:RBRACE,[:REGEX,/regex/],:COLON,:LBRACE,:NAME,:LPAREN,:STRING,:RPAREN,:RBRACE,:RBRACE - ) - end - end - - it "should return the REGEX token and a Regexp" do - @token.convert(stub("lexer"), "/myregex/").should == [Puppet::Pops::Parser::Lexer::TOKENS[:REGEX], Regexp.new(/myregex/)] - end -end - -describe Puppet::Pops::Parser::Lexer, "when lexing comments" do - before { @lexer = Puppet::Pops::Parser::Lexer.new } - - it "should skip whitespace before lexing the next token after a non-token" do - EgrammarLexerSpec.tokens_scanned_from("/* 1\n\n */ \ntest").should be_like([:NAME, "test"]) - end -end - -# FIXME: We need to rewrite all of these tests, but I just don't want to take the time right now. -describe "Puppet::Pops::Parser::Lexer in the old tests" do - before { @lexer = Puppet::Pops::Parser::Lexer.new } - - it "should do simple lexing" do - { - %q{\\} => [[:BACKSLASH,"\\"]], - %q{simplest scanner test} => [[:NAME,"simplest"],[:NAME,"scanner"],[:NAME,"test"]], - %Q{returned scanner test\n} => [[:NAME,"returned"],[:NAME,"scanner"],[:NAME,"test"]] - }.each { |source,expected| - EgrammarLexerSpec.tokens_scanned_from(source).should be_like(*expected) - } - end - - it "should fail usefully" do - expect { EgrammarLexerSpec.tokens_scanned_from('^') }.to raise_error(RuntimeError) - end - - it "should fail if the string is not set" do - expect { @lexer.fullscan }.to raise_error(Puppet::LexError) - end - - it "should correctly identify keywords" do - EgrammarLexerSpec.tokens_scanned_from("case").should be_like([:CASE, "case"]) - end - - it "should correctly parse class references" do - %w{Many Different Words A Word}.each { |t| EgrammarLexerSpec.tokens_scanned_from(t).should be_like([:CLASSREF,t])} - end - - # #774 - it "should correctly parse namespaced class refernces token" do - %w{Foo ::Foo Foo::Bar ::Foo::Bar}.each { |t| EgrammarLexerSpec.tokens_scanned_from(t).should be_like([:CLASSREF, t]) } - end - - it "should correctly parse names" do - %w{this is a bunch of names}.each { |t| EgrammarLexerSpec.tokens_scanned_from(t).should be_like([:NAME,t]) } - end - - it "should correctly parse names with numerals" do - %w{1name name1 11names names11}.each { |t| EgrammarLexerSpec.tokens_scanned_from(t).should be_like([:NAME,t]) } - end - - it "should correctly parse empty strings" do - expect { EgrammarLexerSpec.tokens_scanned_from('$var = ""') }.to_not raise_error - end - - it "should correctly parse virtual resources" do - EgrammarLexerSpec.tokens_scanned_from("@type {").should be_like([:AT, "@"], [:NAME, "type"], [:LBRACE, "{"]) - end - - it "should correctly deal with namespaces" do - @lexer.string = %{class myclass} - @lexer.fullscan - @lexer.namespace.should == "myclass" - - @lexer.namepop - @lexer.namespace.should == "" - - @lexer.string = "class base { class sub { class more" - @lexer.fullscan - @lexer.namespace.should == "base::sub::more" - - @lexer.namepop - @lexer.namespace.should == "base::sub" - end - - it "should not put class instantiation on the namespace" do - @lexer.string = "class base { class sub { class { mode" - @lexer.fullscan - @lexer.namespace.should == "base::sub" - end - - it "should correctly handle fully qualified names" do - @lexer.string = "class base { class sub::more {" - @lexer.fullscan - @lexer.namespace.should == "base::sub::more" - - @lexer.namepop - @lexer.namespace.should == "base" - end - - it "should correctly lex variables" do - ["$variable", "$::variable", "$qualified::variable", "$further::qualified::variable"].each do |string| - EgrammarLexerSpec.tokens_scanned_from(string).should be_like([:VARIABLE,string.sub(/^\$/,'')]) - end - end - - it "should end variables at `-`" do - EgrammarLexerSpec.tokens_scanned_from('$hyphenated-variable'). - should be_like([:VARIABLE, "hyphenated"], [:MINUS, '-'], [:NAME, 'variable']) - end - - it "should not include whitespace in a variable" do - EgrammarLexerSpec.tokens_scanned_from("$foo bar").should_not be_like([:VARIABLE, "foo bar"]) - end - it "should not include excess colons in a variable" do - EgrammarLexerSpec.tokens_scanned_from("$foo::::bar").should_not be_like([:VARIABLE, "foo::::bar"]) - end -end - -describe "Puppet::Pops::Parser::Lexer in the old tests when lexing example files" do - my_fixtures('*.pp') do |file| - it "should correctly lex #{file}" do - lexer = Puppet::Pops::Parser::Lexer.new - lexer.file = file - expect { lexer.fullscan }.to_not raise_error - end - end -end - -describe "when trying to lex a non-existent file" do - include PuppetSpec::Files - - it "should return an empty list of tokens" do - lexer = Puppet::Pops::Parser::Lexer.new - lexer.file = nofile = tmpfile('lexer') - Puppet::FileSystem.exist?(nofile).should == false - - lexer.fullscan.should == [[false,false]] - end -end - -describe "when string quotes are not closed" do - it "should report with message including an \" opening quote" do - expect { EgrammarLexerSpec.tokens_scanned_from('$var = "') }.to raise_error(/after '"'/) - end - - it "should report with message including an \' opening quote" do - expect { EgrammarLexerSpec.tokens_scanned_from('$var = \'') }.to raise_error(/after "'"/) - end - - it "should report <eof> if immediately followed by eof" do - expect { EgrammarLexerSpec.tokens_scanned_from('$var = "') }.to raise_error(/followed by '<eof>'/) - end - - it "should report max 5 chars following quote" do - expect { EgrammarLexerSpec.tokens_scanned_from('$var = "123456') }.to raise_error(/followed by '12345...'/) - end - - it "should escape control chars" do - expect { EgrammarLexerSpec.tokens_scanned_from('$var = "12\n3456') }.to raise_error(/followed by '12\\n3...'/) - end - - it "should resport position of opening quote" do - expect { EgrammarLexerSpec.tokens_scanned_from('$var = "123456') }.to raise_error(/at line 1:8/) - expect { EgrammarLexerSpec.tokens_scanned_from('$var = "123456') }.to raise_error(/at line 1:9/) - end -end - -describe "when lexing number, bad input should not go unpunished" do - it "should slap bad octal as such" do - expect { EgrammarLexerSpec.tokens_scanned_from('$var = 0778') }.to raise_error(/Not a valid octal/) - end - - it "should slap bad hex as such" do - expect { EgrammarLexerSpec.tokens_scanned_from('$var = 0xFG') }.to raise_error(/Not a valid hex/) - expect { EgrammarLexerSpec.tokens_scanned_from('$var = 0xfg') }.to raise_error(/Not a valid hex/) - end - # Note, bad decimals are probably impossible to enter, as they are not recognized as complete numbers, instead, - # the error will be something else, depending on what follows some initial digit. - # -end - -describe "when lexing interpolation detailed positioning should be correct" do - it "should correctly position a string without interpolation" do - EgrammarLexerSpec.tokens_scanned_from('"not interpolated"').should be_like( - [:STRING, {:value=>"not interpolated", :line=>1, :offset=>0, :pos=>1, :length=>18}]) - end - - it "should correctly position a string with false start in interpolation" do - EgrammarLexerSpec.tokens_scanned_from('"not $$$ rpolated"').should be_like( - [:STRING, {:value=>"not $$$ rpolated", :line=>1, :offset=>0, :pos=>1, :length=>18}]) - end - - it "should correctly position pre-mid-end interpolation " do - EgrammarLexerSpec.tokens_scanned_from('"pre $x mid $y end"').should be_like( - [:DQPRE, {:value=>"pre ", :line=>1, :offset=>0, :pos=>1, :length=>6}], - [:VARIABLE, {:value=>"x", :line=>1, :offset=>6, :pos=>7, :length=>1}], - [:DQMID, {:value=>" mid ", :line=>1, :offset=>7, :pos=>8, :length=>6}], - [:VARIABLE, {:value=>"y", :line=>1, :offset=>13, :pos=>14, :length=>1}], - [:DQPOST, {:value=>" end", :line=>1, :offset=>14, :pos=>15, :length=>5}] - ) - end - - it "should correctly position pre-mid-end interpolation using ${} " do - EgrammarLexerSpec.tokens_scanned_from('"pre ${x} mid ${y} end"').should be_like( - [:DQPRE, {:value=>"pre ", :line=>1, :offset=>0, :pos=>1, :length=>7}], - [:VARIABLE, {:value=>"x", :line=>1, :offset=>7, :pos=>8, :length=>1}], - [:DQMID, {:value=>" mid ", :line=>1, :offset=>8, :pos=>9, :length=>8}], - [:VARIABLE, {:value=>"y", :line=>1, :offset=>16, :pos=>17, :length=>1}], - [:DQPOST, {:value=>" end", :line=>1, :offset=>17, :pos=>18, :length=>6}] - ) - end - - it "should correctly position pre-end interpolation using ${} with f call" do - EgrammarLexerSpec.tokens_scanned_from('"pre ${x()} end"').should be_like( - [:DQPRE, {:value=>"pre ", :line=>1, :offset=>0, :pos=>1, :length=>7}], - [:NAME, {:value=>"x", :line=>1, :offset=>7, :pos=>8, :length=>1}], - [:LPAREN, {:value=>"(", :line=>1, :offset=>8, :pos=>9, :length=>1}], - [:RPAREN, {:value=>")", :line=>1, :offset=>9, :pos=>10, :length=>1}], - [:DQPOST, {:value=>" end", :line=>1, :offset=>10, :pos=>11, :length=>6}] - ) - end - - it "should correctly position pre-end interpolation using ${} with $x" do - EgrammarLexerSpec.tokens_scanned_from('"pre ${$x} end"').should be_like( - [:DQPRE, {:value=>"pre ", :line=>1, :offset=>0, :pos=>1, :length=>7}], - [:VARIABLE, {:value=>"x", :line=>1, :offset=>7, :pos=>8, :length=>2}], - [:DQPOST, {:value=>" end", :line=>1, :offset=>9, :pos=>10, :length=>6}] - ) - end - - it "should correctly position pre-end interpolation across lines" do - EgrammarLexerSpec.tokens_scanned_from(%Q["pre ${\n$x} end"]).should be_like( - [:DQPRE, {:value=>"pre ", :line=>1, :offset=>0, :pos=>1, :length=>7}], - [:VARIABLE, {:value=>"x", :line=>2, :offset=>8, :pos=>1, :length=>2}], - [:DQPOST, {:value=>" end", :line=>2, :offset=>10, :pos=>3, :length=>6}] - ) - end - - it "should correctly position interpolation across lines when strings have embedded newlines" do - EgrammarLexerSpec.tokens_scanned_from(%Q["pre \n\n${$x}\n mid$y"]).should be_like( - [:DQPRE, {:value=>"pre \n\n", :line=>1, :offset=>0, :pos=>1, :length=>9}], - [:VARIABLE, {:value=>"x", :line=>3, :offset=>9, :pos=>3, :length=>2}], - [:DQMID, {:value=>"\n mid", :line=>3, :offset=>11, :pos=>5, :length=>7}], - [:VARIABLE, {:value=>"y", :line=>4, :offset=>18, :pos=>6, :length=>1}] - ) - end -end diff --git a/spec/unit/pops/parser/parse_basic_expressions_spec.rb b/spec/unit/pops/parser/parse_basic_expressions_spec.rb index f5aeb2a29..2190e54bb 100644 --- a/spec/unit/pops/parser/parse_basic_expressions_spec.rb +++ b/spec/unit/pops/parser/parse_basic_expressions_spec.rb @@ -134,6 +134,11 @@ describe "egrammar parsing basic expressions" do it "$a = 'a' !~ 'b.*'" do; dump(parse("$a = 'a' !~ 'b.*'")).should == "(= $a (!~ 'a' 'b.*'))" ; end end + context "When parsing unfold" do + it "$a = *[1,2]" do; dump(parse("$a = *[1,2]")).should == "(= $a (unfold ([] 1 2)))" ; end + it "$a = *1" do; dump(parse("$a = *1")).should == "(= $a (unfold 1))" ; end + end + context "When parsing Lists" do it "$a = []" do dump(parse("$a = []")).should == "(= $a ([]))" diff --git a/spec/unit/pops/parser/parse_calls_spec.rb b/spec/unit/pops/parser/parse_calls_spec.rb index 115c160d6..ee80544f5 100644 --- a/spec/unit/pops/parser/parse_calls_spec.rb +++ b/spec/unit/pops/parser/parse_calls_spec.rb @@ -66,7 +66,7 @@ describe "egrammar parsing function calls" do # For egrammar where a bare word can be a "statement" it "$a = foo bar # illegal, must have parentheses" do - dump(parse("$a = foo bar")).should == "(block (= $a foo) bar)" + dump(parse("$a = foo bar")).should == "(block\n (= $a foo)\n bar\n)" end context "in nested scopes" do @@ -94,8 +94,11 @@ describe "egrammar parsing function calls" do end it "$a.foo |$x|{ }" do - dump(parse("$a.foo |$x|{ $b = $x}")).should == - "(call-method (. $a foo) (lambda (parameters x) (block (= $b $x))))" + dump(parse("$a.foo |$x|{ $b = $x}")).should == [ + "(call-method (. $a foo) (lambda (parameters x) (block", + " (= $b $x)", + ")))" + ].join("\n") end end end diff --git a/spec/unit/pops/parser/parse_conditionals_spec.rb b/spec/unit/pops/parser/parse_conditionals_spec.rb index b8b8d9c8e..591b20e97 100644 --- a/spec/unit/pops/parser/parse_conditionals_spec.rb +++ b/spec/unit/pops/parser/parse_conditionals_spec.rb @@ -30,10 +30,14 @@ describe "egrammar parsing conditionals" do end it "if true { $a = 10 $b = 10 } else {$a = 20}" do - dump(parse("if true { $a = 10 $b = 20} else {$a = 20}")).should == - ["(if true", - " (then (block (= $a 10) (= $b 20)))", - " (else (= $a 20)))"].join("\n") + dump(parse("if true { $a = 10 $b = 20} else {$a = 20}")).should == [ + "(if true", + " (then (block", + " (= $a 10)", + " (= $b 20)", + " ))", + " (else (= $a 20)))" + ].join("\n") end it "allows a parenthesized conditional expression" do @@ -142,7 +146,10 @@ describe "egrammar parsing conditionals" do it "case $a { a : {$b = 10 $c = 20}}" do dump(parse("case $a { a : {$b = 10 $c = 20}}")).should == ["(case $a", - " (when (a) (then (block (= $b 10) (= $c 20)))))" + " (when (a) (then (block", + " (= $b 10)", + " (= $c 20)", + " ))))" ].join("\n") end end diff --git a/spec/unit/pops/parser/parse_containers_spec.rb b/spec/unit/pops/parser/parse_containers_spec.rb index 57d6efee9..a05c5975d 100644 --- a/spec/unit/pops/parser/parse_containers_spec.rb +++ b/spec/unit/pops/parser/parse_containers_spec.rb @@ -10,7 +10,12 @@ describe "egrammar parsing containers" do context "When parsing file scope" do it "$a = 10 $b = 20" do - dump(parse("$a = 10 $b = 20")).should == "(block (= $a 10) (= $b 20))" + dump(parse("$a = 10 $b = 20")).should == [ + "(block", + " (= $a 10)", + " (= $b 20)", + ")" + ].join("\n") end it "$a = 10" do @@ -24,7 +29,11 @@ describe "egrammar parsing containers" do end it "class foo { class bar {} }" do - dump(parse("class foo { class bar {}}")).should == "(class foo (block (class foo::bar ())))" + dump(parse("class foo { class bar {}}")).should == [ + "(class foo (block", + " (class foo::bar ())", + "))" + ].join("\n") end it "class foo::bar {}" do @@ -52,7 +61,12 @@ describe "egrammar parsing containers" do end it "class foo {$a = 10 $b = 20}" do - dump(parse("class foo {$a = 10 $b = 20}")).should == "(class foo (block (= $a 10) (= $b 20)))" + dump(parse("class foo {$a = 10 $b = 20}")).should == [ + "(class foo (block", + " (= $a 10)", + " (= $b 20)", + "))" + ].join("\n") end context "it should handle '3x weirdness'" do @@ -100,6 +114,16 @@ describe "egrammar parsing containers" do }.to raise_error(/not a valid classname/) end end + + context 'it should allow keywords as attribute names' do + ['and', 'case', 'class', 'default', 'define', 'else', 'elsif', 'if', 'in', 'inherits', 'node', 'or', + 'undef', 'unless', 'type', 'attr', 'function', 'private'].each do |keyword| + it "such as #{keyword}" do + expect {parse("class x ($#{keyword}){} class { x: #{keyword} => 1 }")}.to_not raise_error + end + end + end + end context "When the parser parses define" do @@ -108,12 +132,20 @@ describe "egrammar parsing containers" do end it "class foo { define bar {}}" do - dump(parse("class foo {define bar {}}")).should == "(class foo (block (define foo::bar ())))" + dump(parse("class foo {define bar {}}")).should == [ + "(class foo (block", + " (define foo::bar ())", + "))" + ].join("\n") end it "define foo { define bar {}}" do # This is illegal, but handled as part of validation - dump(parse("define foo { define bar {}}")).should == "(define foo (block (define bar ())))" + dump(parse("define foo { define bar {}}")).should == [ + "(define foo (block", + " (define bar ())", + "))" + ].join("\n") end it "define foo::bar {}" do @@ -133,7 +165,12 @@ describe "egrammar parsing containers" do end it "define foo {$a = 10 $b = 20}" do - dump(parse("define foo {$a = 10 $b = 20}")).should == "(define foo (block (= $a 10) (= $b 20)))" + dump(parse("define foo {$a = 10 $b = 20}")).should == [ + "(define foo (block", + " (= $a 10)", + " (= $b 20)", + "))" + ].join("\n") end context "it should handle '3x weirdness'" do @@ -152,6 +189,15 @@ describe "egrammar parsing containers" do expect { dump(parse("define default {}")).should == "(define default ())"}.to raise_error(Puppet::ParseError) end end + + context 'it should allow keywords as attribute names' do + ['and', 'case', 'class', 'default', 'define', 'else', 'elsif', 'if', 'in', 'inherits', 'node', 'or', + 'undef', 'unless', 'type', 'attr', 'function', 'private'].each do |keyword| + it "such as #{keyword}" do + expect {parse("define x ($#{keyword}){} x { y: #{keyword} => 1 }")}.to_not raise_error + end + end + end end context "When parsing node" do @@ -159,6 +205,10 @@ describe "egrammar parsing containers" do dump(parse("node foo {}")).should == "(node (matches 'foo') ())" end + it "node foo, {} # trailing comma" do + dump(parse("node foo, {}")).should == "(node (matches 'foo') ())" + end + it "node kermit.example.com {}" do dump(parse("node kermit.example.com {}")).should == "(node (matches 'kermit.example.com') ())" end @@ -200,7 +250,12 @@ describe "egrammar parsing containers" do end it "node foo inherits bar {$a = 10 $b = 20}" do - dump(parse("node foo inherits bar {$a = 10 $b = 20}")).should == "(node (matches 'foo') (parent 'bar') (block (= $a 10) (= $b 20)))" + dump(parse("node foo inherits bar {$a = 10 $b = 20}")).should == [ + "(node (matches 'foo') (parent 'bar') (block", + " (= $a 10)", + " (= $b 20)", + "))" + ].join("\n") end end end diff --git a/spec/unit/pops/parser/parse_resource_spec.rb b/spec/unit/pops/parser/parse_resource_spec.rb index ee7e13445..cf7e7cb01 100644 --- a/spec/unit/pops/parser/parse_resource_spec.rb +++ b/spec/unit/pops/parser/parse_resource_spec.rb @@ -9,72 +9,98 @@ describe "egrammar parsing resource declarations" do include ParserRspecHelper context "When parsing regular resource" do - it "file { 'title': }" do - dump(parse("file { 'title': }")).should == [ - "(resource file", - " ('title'))" - ].join("\n") - end + ["File", "file"].each do |word| + it "#{word} { 'title': }" do + dump(parse("#{word} { 'title': }")).should == [ + "(resource file", + " ('title'))" + ].join("\n") + end - it "file { 'title': path => '/somewhere', mode => 0777}" do - dump(parse("file { 'title': path => '/somewhere', mode => 0777}")).should == [ - "(resource file", - " ('title'", - " (path => '/somewhere')", - " (mode => 0777)))" - ].join("\n") - end + it "#{word} { 'title': path => '/somewhere', mode => '0777'}" do + dump(parse("#{word} { 'title': path => '/somewhere', mode => '0777'}")).should == [ + "(resource file", + " ('title'", + " (path => '/somewhere')", + " (mode => '0777')))" + ].join("\n") + end - it "file { 'title': path => '/somewhere', }" do - dump(parse("file { 'title': path => '/somewhere', }")).should == [ - "(resource file", - " ('title'", - " (path => '/somewhere')))" - ].join("\n") - end + it "#{word} { 'title': path => '/somewhere', }" do + dump(parse("#{word} { 'title': path => '/somewhere', }")).should == [ + "(resource file", + " ('title'", + " (path => '/somewhere')))" + ].join("\n") + end - it "file { 'title': , }" do - dump(parse("file { 'title': , }")).should == [ - "(resource file", - " ('title'))" - ].join("\n") - end + it "#{word} { 'title': , }" do + dump(parse("#{word} { 'title': , }")).should == [ + "(resource file", + " ('title'))" + ].join("\n") + end - it "file { 'title': ; }" do - dump(parse("file { 'title': ; }")).should == [ - "(resource file", - " ('title'))" - ].join("\n") - end + it "#{word} { 'title': ; }" do + dump(parse("#{word} { 'title': ; }")).should == [ + "(resource file", + " ('title'))" + ].join("\n") + end - it "file { 'title': ; 'other_title': }" do - dump(parse("file { 'title': ; 'other_title': }")).should == [ - "(resource file", - " ('title')", - " ('other_title'))" - ].join("\n") - end + it "#{word} { 'title': ; 'other_title': }" do + dump(parse("#{word} { 'title': ; 'other_title': }")).should == [ + "(resource file", + " ('title')", + " ('other_title'))" + ].join("\n") + end - it "file { 'title1': path => 'x'; 'title2': path => 'y'}" do - dump(parse("file { 'title1': path => 'x'; 'title2': path => 'y'}")).should == [ - "(resource file", - " ('title1'", - " (path => 'x'))", - " ('title2'", - " (path => 'y')))", - ].join("\n") + # PUP-2898, trailing ';' + it "#{word} { 'title': ; 'other_title': ; }" do + dump(parse("#{word} { 'title': ; 'other_title': ; }")).should == [ + "(resource file", + " ('title')", + " ('other_title'))" + ].join("\n") + end + + it "#{word} { 'title1': path => 'x'; 'title2': path => 'y'}" do + dump(parse("#{word} { 'title1': path => 'x'; 'title2': path => 'y'}")).should == [ + "(resource file", + " ('title1'", + " (path => 'x'))", + " ('title2'", + " (path => 'y')))", + ].join("\n") + end + + it "#{word} { title: * => {mode => '0777'} }" do + dump(parse("#{word} { title: * => {mode => '0777'}}")).should == [ + "(resource file", + " (title", + " (* => ({} (mode '0777')))))" + ].join("\n") + end end end - context "When parsing resource defaults" do + context "When parsing (type based) resource defaults" do it "File { }" do dump(parse("File { }")).should == "(resource-defaults file)" end - it "File { mode => 0777 }" do - dump(parse("File { mode => 0777}")).should == [ + it "File { mode => '0777' }" do + dump(parse("File { mode => '0777'}")).should == [ "(resource-defaults file", - " (mode => 0777))" + " (mode => '0777'))" + ].join("\n") + end + + it "File { * => {mode => '0777'} } (even if validated to be illegal)" do + dump(parse("File { * => {mode => '0777'}}")).should == [ + "(resource-defaults file", + " (* => ({} (mode '0777'))))" ].join("\n") end end @@ -85,36 +111,92 @@ describe "egrammar parsing resource declarations" do end it "File['x'] { x => 1 }" do - dump(parse("File['x'] { x => 1}")).should == "(override (slice file 'x')\n (x => 1))" + dump(parse("File['x'] { x => 1}")).should == [ + "(override (slice file 'x')", + " (x => 1))" + ].join("\n") end + it "File['x', 'y'] { x => 1 }" do - dump(parse("File['x', 'y'] { x => 1}")).should == "(override (slice file ('x' 'y'))\n (x => 1))" + dump(parse("File['x', 'y'] { x => 1}")).should == [ + "(override (slice file ('x' 'y'))", + " (x => 1))" + ].join("\n") end it "File['x'] { x => 1, y => 2 }" do - dump(parse("File['x'] { x => 1, y=> 2}")).should == "(override (slice file 'x')\n (x => 1)\n (y => 2))" + dump(parse("File['x'] { x => 1, y=> 2}")).should == [ + "(override (slice file 'x')", + " (x => 1)", + " (y => 2))" + ].join("\n") end it "File['x'] { x +> 1 }" do - dump(parse("File['x'] { x +> 1}")).should == "(override (slice file 'x')\n (x +> 1))" + dump(parse("File['x'] { x +> 1}")).should == [ + "(override (slice file 'x')", + " (x +> 1))" + ].join("\n") + end + + it "File['x'] { * => {mode => '0777'} } (even if validated to be illegal)" do + dump(parse("File['x'] { * => {mode => '0777'}}")).should == [ + "(override (slice file 'x')", + " (* => ({} (mode '0777'))))" + ].join("\n") end end context "When parsing virtual and exported resources" do - it "@@file { 'title': }" do + it "parses exported @@file { 'title': }" do dump(parse("@@file { 'title': }")).should == "(exported-resource file\n ('title'))" end - it "@file { 'title': }" do + it "parses virtual @file { 'title': }" do dump(parse("@file { 'title': }")).should == "(virtual-resource file\n ('title'))" end - it "@file { mode => 0777 }" do - # Defaults are not virtualizeable - expect { - dump(parse("@file { mode => 0777 }")).should == "" - }.to raise_error(Puppet::ParseError, /Defaults are not virtualizable/) + it "nothing before the title colon is a syntax error" do + expect do + parse("@file {: mode => '0777' }") + end.to raise_error(/Syntax error/) + end + + it "raises error for user error; not a resource" do + # The expression results in VIRTUAL, CALL FUNCTION('file', HASH) since the resource body has + # no title. + expect do + parse("@file { mode => '0777' }") + end.to raise_error(/Virtual \(@\) can only be applied to a Resource Expression/) + end + + it "parses global defaults with @ (even if validated to be illegal)" do + dump(parse("@File { mode => '0777' }")).should == [ + "(virtual-resource-defaults file", + " (mode => '0777'))" + ].join("\n") + end + + it "parses global defaults with @@ (even if validated to be illegal)" do + dump(parse("@@File { mode => '0777' }")).should == [ + "(exported-resource-defaults file", + " (mode => '0777'))" + ].join("\n") + end + + it "parses override with @ (even if validated to be illegal)" do + dump(parse("@File[foo] { mode => '0777' }")).should == [ + "(virtual-override (slice file foo)", + " (mode => '0777'))" + ].join("\n") + end + + it "parses override combined with @@ (even if validated to be illegal)" do + dump(parse("@@File[foo] { mode => '0777' }")).should == [ + "(exported-override (slice file foo)", + " (mode => '0777'))" + ].join("\n") end end @@ -220,22 +302,22 @@ describe "egrammar parsing resource declarations" do dump(parse("File <| tag == 'foo' |>")).should == "(collect file\n (<| |> (== tag 'foo')))" end - it "File <| tag == 'foo' and mode != 0777 |>" do - dump(parse("File <| tag == 'foo' and mode != 0777 |>")).should == "(collect file\n (<| |> (&& (== tag 'foo') (!= mode 0777))))" + it "File <| tag == 'foo' and mode != '0777' |>" do + dump(parse("File <| tag == 'foo' and mode != '0777' |>")).should == "(collect file\n (<| |> (&& (== tag 'foo') (!= mode '0777'))))" end - it "File <| tag == 'foo' or mode != 0777 |>" do - dump(parse("File <| tag == 'foo' or mode != 0777 |>")).should == "(collect file\n (<| |> (|| (== tag 'foo') (!= mode 0777))))" + it "File <| tag == 'foo' or mode != '0777' |>" do + dump(parse("File <| tag == 'foo' or mode != '0777' |>")).should == "(collect file\n (<| |> (|| (== tag 'foo') (!= mode '0777'))))" end - it "File <| tag == 'foo' or tag == 'bar' and mode != 0777 |>" do - dump(parse("File <| tag == 'foo' or tag == 'bar' and mode != 0777 |>")).should == - "(collect file\n (<| |> (|| (== tag 'foo') (&& (== tag 'bar') (!= mode 0777)))))" + it "File <| tag == 'foo' or tag == 'bar' and mode != '0777' |>" do + dump(parse("File <| tag == 'foo' or tag == 'bar' and mode != '0777' |>")).should == + "(collect file\n (<| |> (|| (== tag 'foo') (&& (== tag 'bar') (!= mode '0777')))))" end - it "File <| (tag == 'foo' or tag == 'bar') and mode != 0777 |>" do - dump(parse("File <| (tag == 'foo' or tag == 'bar') and mode != 0777 |>")).should == - "(collect file\n (<| |> (&& (|| (== tag 'foo') (== tag 'bar')) (!= mode 0777))))" + it "File <| (tag == 'foo' or tag == 'bar') and mode != '0777' |>" do + dump(parse("File <| (tag == 'foo' or tag == 'bar') and mode != '0777' |>")).should == + "(collect file\n (<| |> (&& (|| (== tag 'foo') (== tag 'bar')) (!= mode '0777'))))" end end end diff --git a/spec/unit/pops/parser/parser_spec.rb b/spec/unit/pops/parser/parser_spec.rb index fb44a08c9..23f537eeb 100644 --- a/spec/unit/pops/parser/parser_spec.rb +++ b/spec/unit/pops/parser/parser_spec.rb @@ -14,4 +14,20 @@ describe Puppet::Pops::Parser::Parser do model.body.class.should == Puppet::Pops::Model::AssignmentExpression end + it "should accept empty input and return a model" do + parser = Puppet::Pops::Parser::Parser.new() + model = parser.parse_string("").current + model.class.should == Puppet::Pops::Model::Program + model.body.class.should == Puppet::Pops::Model::Nop + end + + it "should accept empty input containing only comments and report location at end of input" do + parser = Puppet::Pops::Parser::Parser.new() + model = parser.parse_string("# comment\n").current + model.class.should == Puppet::Pops::Model::Program + model.body.class.should == Puppet::Pops::Model::Nop + adapter = Puppet::Pops::Adapters::SourcePosAdapter.adapt(model.body) + expect(adapter.offset).to eq(10) + expect(adapter.length).to eq(0) + end end diff --git a/spec/unit/pops/parser/parsing_typed_parameters_spec.rb b/spec/unit/pops/parser/parsing_typed_parameters_spec.rb new file mode 100644 index 000000000..678f6523c --- /dev/null +++ b/spec/unit/pops/parser/parsing_typed_parameters_spec.rb @@ -0,0 +1,72 @@ +require 'spec_helper' + +require 'puppet/pops' +require 'puppet/pops/evaluator/evaluator_impl' +require 'puppet_spec/pops' +require 'puppet_spec/scope' +require 'puppet/parser/e4_parser_adapter' + + +# relative to this spec file (./) does not work as this file is loaded by rspec +#require File.join(File.dirname(__FILE__), '/evaluator_rspec_helper') + +describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do + include PuppetSpec::Pops + include PuppetSpec::Scope + before(:each) do + + # These must be set since the is 3x logic that triggers on these even if the tests are explicit + # about selection of parser and evaluator + # + Puppet[:parser] = 'future' + end + + let(:parser) { Puppet::Pops::Parser::EvaluatingParser.new } + + context "captures-rest parameter" do + it 'is allowed in lambda when placed last' do + source = <<-CODE + foo() |$a, *$b| { $a + $b[0] } + CODE + expect do + parser.parse_string(source, __FILE__) + end.to_not raise_error() + end + + it 'allows a type annotation' do + source = <<-CODE + foo() |$a, Integer *$b| { $a + $b[0] } + CODE + expect do + parser.parse_string(source, __FILE__) + end.to_not raise_error() + end + + it 'is not allowed in lambda except last' do + source = <<-CODE + foo() |*$a, $b| { $a + $b[0] } + CODE + expect do + parser.parse_string(source, __FILE__) + end.to raise_error(Puppet::ParseError, /Parameter \$a is not last, and has 'captures rest'/) + end + + it 'is not allowed in define' do + source = <<-CODE + define foo(*$a) { } + CODE + expect do + parser.parse_string(source, __FILE__) + end.to raise_error(Puppet::ParseError, /Parameter \$a has 'captures rest' - not supported in a 'define'/) + end + + it 'is not allowed in class' do + source = <<-CODE + class foo(*$a) { } + CODE + expect do + parser.parse_string(source, __FILE__) + end.to raise_error(Puppet::ParseError, /Parameter \$a has 'captures rest' - not supported in a Host Class Definition/) + end + end +end diff --git a/spec/unit/pops/transformer/transform_calls_spec.rb b/spec/unit/pops/transformer/transform_calls_spec.rb index f58c79e1e..dba0d560f 100644 --- a/spec/unit/pops/transformer/transform_calls_spec.rb +++ b/spec/unit/pops/transformer/transform_calls_spec.rb @@ -34,6 +34,7 @@ describe "transformation to Puppet AST for function calls" do "realize bar" => '(invoke realize bar)', "contain bar" => '(invoke contain bar)', "include bar" => '(invoke include bar)', + "tag bar" => '(invoke tag bar)', "info bar" => '(invoke info bar)', "notice bar" => '(invoke notice bar)', @@ -54,7 +55,6 @@ describe "transformation to Puppet AST for function calls" do { "foo bar" => '(block foo bar)', - "tag bar" => '(block tag bar)', "tag" => 'tag', }.each do |source, result| it "should not transform #{source}, and instead produce #{result}" do diff --git a/spec/unit/pops/transformer/transform_resource_spec.rb b/spec/unit/pops/transformer/transform_resource_spec.rb deleted file mode 100644 index 251ef2f75..000000000 --- a/spec/unit/pops/transformer/transform_resource_spec.rb +++ /dev/null @@ -1,185 +0,0 @@ -#! /usr/bin/env ruby -require 'spec_helper' -require 'puppet/pops' - -# relative to this spec file (./) does not work as this file is loaded by rspec -require File.join(File.dirname(__FILE__), '/transformer_rspec_helper') - -describe "transformation to Puppet AST for resource declarations" do - include TransformerRspecHelper - - context "When transforming regular resource" do - it "file { 'title': }" do - astdump(parse("file { 'title': }")).should == [ - "(resource file", - " ('title'))" - ].join("\n") - end - - it "file { 'title': ; 'other_title': }" do - astdump(parse("file { 'title': ; 'other_title': }")).should == [ - "(resource file", - " ('title')", - " ('other_title'))" - ].join("\n") - end - - it "file { 'title': path => '/somewhere', mode => 0777}" do - astdump(parse("file { 'title': path => '/somewhere', mode => 0777}")).should == [ - "(resource file", - " ('title'", - " (path => '/somewhere')", - " (mode => 0777)))" - ].join("\n") - end - - it "file { 'title1': path => 'x'; 'title2': path => 'y'}" do - astdump(parse("file { 'title1': path => 'x'; 'title2': path => 'y'}")).should == [ - "(resource file", - " ('title1'", - " (path => 'x'))", - " ('title2'", - " (path => 'y')))", - ].join("\n") - end - end - - context "When transforming resource defaults" do - it "File { }" do - astdump(parse("File { }")).should == "(resource-defaults file)" - end - - it "File { mode => 0777 }" do - astdump(parse("File { mode => 0777}")).should == [ - "(resource-defaults file", - " (mode => 0777))" - ].join("\n") - end - end - - context "When transforming resource override" do - it "File['x'] { }" do - astdump(parse("File['x'] { }")).should == "(override (slice file 'x'))" - end - - it "File['x'] { x => 1 }" do - astdump(parse("File['x'] { x => 1}")).should == "(override (slice file 'x')\n (x => 1))" - end - - it "File['x', 'y'] { x => 1 }" do - astdump(parse("File['x', 'y'] { x => 1}")).should == "(override (slice file ('x' 'y'))\n (x => 1))" - end - - it "File['x'] { x => 1, y => 2 }" do - astdump(parse("File['x'] { x => 1, y=> 2}")).should == "(override (slice file 'x')\n (x => 1)\n (y => 2))" - end - - it "File['x'] { x +> 1 }" do - astdump(parse("File['x'] { x +> 1}")).should == "(override (slice file 'x')\n (x +> 1))" - end - end - - context "When transforming virtual and exported resources" do - it "@@file { 'title': }" do - astdump(parse("@@file { 'title': }")).should == "(exported-resource file\n ('title'))" - end - - it "@file { 'title': }" do - astdump(parse("@file { 'title': }")).should == "(virtual-resource file\n ('title'))" - end - end - - context "When transforming class resource" do - it "class { 'cname': }" do - astdump(parse("class { 'cname': }")).should == [ - "(resource class", - " ('cname'))" - ].join("\n") - end - - it "class { 'cname': x => 1, y => 2}" do - astdump(parse("class { 'cname': x => 1, y => 2}")).should == [ - "(resource class", - " ('cname'", - " (x => 1)", - " (y => 2)))" - ].join("\n") - end - - it "class { 'cname1': x => 1; 'cname2': y => 2}" do - astdump(parse("class { 'cname1': x => 1; 'cname2': y => 2}")).should == [ - "(resource class", - " ('cname1'", - " (x => 1))", - " ('cname2'", - " (y => 2)))", - ].join("\n") - end - end - - context "When transforming Relationships" do - it "File[a] -> File[b]" do - astdump(parse("File[a] -> File[b]")).should == "(-> (slice file a) (slice file b))" - end - - it "File[a] <- File[b]" do - astdump(parse("File[a] <- File[b]")).should == "(<- (slice file a) (slice file b))" - end - - it "File[a] ~> File[b]" do - astdump(parse("File[a] ~> File[b]")).should == "(~> (slice file a) (slice file b))" - end - - it "File[a] <~ File[b]" do - astdump(parse("File[a] <~ File[b]")).should == "(<~ (slice file a) (slice file b))" - end - - it "Should chain relationships" do - astdump(parse("a -> b -> c")).should == - "(-> (-> a b) c)" - end - - it "Should chain relationships" do - astdump(parse("File[a] -> File[b] ~> File[c] <- File[d] <~ File[e]")).should == - "(<~ (<- (~> (-> (slice file a) (slice file b)) (slice file c)) (slice file d)) (slice file e))" - end - end - - context "When transforming collection" do - context "of virtual resources" do - it "File <| |>" do - astdump(parse("File <| |>")).should == "(collect file\n (<| |>))" - end - end - - context "of exported resources" do - it "File <<| |>>" do - astdump(parse("File <<| |>>")).should == "(collect file\n (<<| |>>))" - end - end - - context "queries are parsed with correct precedence" do - it "File <| tag == 'foo' |>" do - astdump(parse("File <| tag == 'foo' |>")).should == "(collect file\n (<| |> (== tag 'foo')))" - end - - it "File <| tag == 'foo' and mode != 0777 |>" do - astdump(parse("File <| tag == 'foo' and mode != 0777 |>")).should == "(collect file\n (<| |> (&& (== tag 'foo') (!= mode 0777))))" - end - - it "File <| tag == 'foo' or mode != 0777 |>" do - astdump(parse("File <| tag == 'foo' or mode != 0777 |>")).should == "(collect file\n (<| |> (|| (== tag 'foo') (!= mode 0777))))" - end - - it "File <| tag == 'foo' or tag == 'bar' and mode != 0777 |>" do - astdump(parse("File <| tag == 'foo' or tag == 'bar' and mode != 0777 |>")).should == - "(collect file\n (<| |> (|| (== tag 'foo') (&& (== tag 'bar') (!= mode 0777)))))" - end - - it "File <| (tag == 'foo' or tag == 'bar') and mode != 0777 |>" do - astdump(parse("File <| (tag == 'foo' or tag == 'bar') and mode != 0777 |>")).should == - "(collect file\n (<| |> (&& (|| (== tag 'foo') (== tag 'bar')) (!= mode 0777))))" - end - end - end -end diff --git a/spec/unit/pops/types/type_calculator_spec.rb b/spec/unit/pops/types/type_calculator_spec.rb index 3c4ea77ca..0bd475263 100644 --- a/spec/unit/pops/types/type_calculator_spec.rb +++ b/spec/unit/pops/types/type_calculator_spec.rb @@ -78,8 +78,13 @@ describe 'The type calculator' do Puppet::Pops::Types::TypeFactory.struct(type_hash) end - def optional_object_t - Puppet::Pops::Types::TypeFactory.optional_object() + def object_t + Puppet::Pops::Types::TypeFactory.any() + end + + def unit_t + # Cannot be created via factory, the type is private to the type system + Puppet::Pops::Types::PUnitType.new end def types @@ -88,8 +93,9 @@ describe 'The type calculator' do shared_context "types_setup" do + # Do not include the special type Unit in this list def all_types - [ Puppet::Pops::Types::PObjectType, + [ Puppet::Pops::Types::PAnyType, Puppet::Pops::Types::PNilType, Puppet::Pops::Types::PDataType, Puppet::Pops::Types::PScalarType, @@ -102,7 +108,7 @@ describe 'The type calculator' do Puppet::Pops::Types::PCollectionType, Puppet::Pops::Types::PArrayType, Puppet::Pops::Types::PHashType, - Puppet::Pops::Types::PRubyType, + Puppet::Pops::Types::PRuntimeType, Puppet::Pops::Types::PHostClassType, Puppet::Pops::Types::PResourceType, Puppet::Pops::Types::PPatternType, @@ -111,6 +117,9 @@ describe 'The type calculator' do Puppet::Pops::Types::PStructType, Puppet::Pops::Types::PTupleType, Puppet::Pops::Types::PCallableType, + Puppet::Pops::Types::PType, + Puppet::Pops::Types::POptionalType, + Puppet::Pops::Types::PDefaultType, ] end @@ -215,17 +224,18 @@ describe 'The type calculator' do calculator.infer(nil).class.should == Puppet::Pops::Types::PNilType end - it ':undef translates to PNilType' do - calculator.infer(:undef).class.should == Puppet::Pops::Types::PNilType + it ':undef translates to PRuntimeType' do + calculator.infer(:undef).class.should == Puppet::Pops::Types::PRuntimeType end - it 'an instance of class Foo translates to PRubyType[Foo]' do + it 'an instance of class Foo translates to PRuntimeType[ruby, Foo]' do class Foo end t = calculator.infer(Foo.new) - t.class.should == Puppet::Pops::Types::PRubyType - t.ruby_class.should == 'Foo' + t.class.should == Puppet::Pops::Types::PRuntimeType + t.runtime.should == :ruby + t.runtime_type_name.should == 'Foo' end context 'array' do @@ -278,8 +288,8 @@ describe 'The type calculator' do calculator.infer(['one', /two/]).element_type.class.should == Puppet::Pops::Types::PScalarType end - it 'with string and symbol values translates to PArrayType[PObjectType]' do - calculator.infer(['one', :two]).element_type.class.should == Puppet::Pops::Types::PObjectType + it 'with string and symbol values translates to PArrayType[PAnyType]' do + calculator.infer(['one', :two]).element_type.class.should == Puppet::Pops::Types::PAnyType end it 'with fixnum and nil values translates to PArrayType[PIntegerType]' do @@ -328,17 +338,18 @@ describe 'The type calculator' do calculator.infer({:first => 1, :second => 2}).class.should == Puppet::Pops::Types::PHashType end - it 'with symbolic keys translates to PHashType[PRubyType[Symbol],value]' do + it 'with symbolic keys translates to PHashType[PRuntimeType[ruby, Symbol], value]' do k = calculator.infer({:first => 1, :second => 2}).key_type - k.class.should == Puppet::Pops::Types::PRubyType - k.ruby_class.should == 'Symbol' + k.class.should == Puppet::Pops::Types::PRuntimeType + k.runtime.should == :ruby + k.runtime_type_name.should == 'Symbol' end - it 'with string keys translates to PHashType[PStringType,value]' do + it 'with string keys translates to PHashType[PStringType, value]' do calculator.infer({'first' => 1, 'second' => 2}).key_type.class.should == Puppet::Pops::Types::PStringType end - it 'with fixnum values translates to PHashType[key,PIntegerType]' do + it 'with fixnum values translates to PHashType[key, PIntegerType]' do calculator.infer({:first => 1, :second => 2}).element_type.class.should == Puppet::Pops::Types::PIntegerType end end @@ -457,14 +468,14 @@ describe 'The type calculator' do expect(common_t.block_type).to be_nil end - it 'compatible instances => the least specific' do + it 'compatible instances => the most specific' do t1 = callable_t(String) scalar_t = Puppet::Pops::Types::PScalarType.new t2 = callable_t(scalar_t) common_t = calculator.common_type(t1, t2) expect(common_t.class).to be(Puppet::Pops::Types::PCallableType) expect(common_t.param_types.class).to be(Puppet::Pops::Types::PTupleType) - expect(common_t.param_types.types).to eql([scalar_t]) + expect(common_t.param_types.types).to eql([string_t]) expect(common_t.block_type).to be_nil end @@ -491,15 +502,33 @@ describe 'The type calculator' do context 'computes assignability' do include_context "types_setup" - context "for Object, such that" do - it 'all types are assignable to Object' do - t = Puppet::Pops::Types::PObjectType.new() + context 'for Unit, such that' do + it 'all types are assignable to Unit' do + t = Puppet::Pops::Types::PUnitType.new() + all_types.each { |t2| t2.new.should be_assignable_to(t) } + end + + it 'Unit is assignable to all other types' do + t = Puppet::Pops::Types::PUnitType.new() + all_types.each { |t2| t.should be_assignable_to(t2.new) } + end + + it 'Unit is assignable to Unit' do + t = Puppet::Pops::Types::PUnitType.new() + t2 = Puppet::Pops::Types::PUnitType.new() + t.should be_assignable_to(t2) + end + end + + context "for Any, such that" do + it 'all types are assignable to Any' do + t = Puppet::Pops::Types::PAnyType.new() all_types.each { |t2| t2.new.should be_assignable_to(t) } end - it 'Object is not assignable to anything but Object' do - tested_types = all_types() - [Puppet::Pops::Types::PObjectType] - t = Puppet::Pops::Types::PObjectType.new() + it 'Any is not assignable to anything but Any' do + tested_types = all_types() - [Puppet::Pops::Types::PAnyType] + t = Puppet::Pops::Types::PAnyType.new() tested_types.each { |t2| t.should_not be_assignable_to(t2.new) } end end @@ -530,7 +559,7 @@ describe 'The type calculator' do end it 'Data is not assignable to any disjunct type' do - tested_types = all_types - [Puppet::Pops::Types::PObjectType, Puppet::Pops::Types::PDataType] - scalar_types + tested_types = all_types - [Puppet::Pops::Types::PAnyType, Puppet::Pops::Types::PDataType] - scalar_types t = Puppet::Pops::Types::PDataType.new() tested_types.each {|t2| t.should_not be_assignable_to(t2.new) } end @@ -549,7 +578,7 @@ describe 'The type calculator' do end it 'Scalar is not assignable to any disjunct type' do - tested_types = all_types - [Puppet::Pops::Types::PObjectType, Puppet::Pops::Types::PDataType] - scalar_types + tested_types = all_types - [Puppet::Pops::Types::PAnyType, Puppet::Pops::Types::PDataType] - scalar_types t = Puppet::Pops::Types::PScalarType.new() tested_types.each {|t2| t.should_not be_assignable_to(t2.new) } end @@ -569,7 +598,7 @@ describe 'The type calculator' do it 'Numeric is not assignable to any disjunct type' do tested_types = all_types - [ - Puppet::Pops::Types::PObjectType, + Puppet::Pops::Types::PAnyType, Puppet::Pops::Types::PDataType, Puppet::Pops::Types::PScalarType, ] - numeric_types @@ -591,7 +620,7 @@ describe 'The type calculator' do end it 'Collection is not assignable to any disjunct type' do - tested_types = all_types - [Puppet::Pops::Types::PObjectType] - collection_types + tested_types = all_types - [Puppet::Pops::Types::PAnyType] - collection_types t = Puppet::Pops::Types::PCollectionType.new() tested_types.each {|t2| t.should_not be_assignable_to(t2.new) } end @@ -609,7 +638,7 @@ describe 'The type calculator' do it 'Array is not assignable to any disjunct type' do tested_types = all_types - [ - Puppet::Pops::Types::PObjectType, + Puppet::Pops::Types::PAnyType, Puppet::Pops::Types::PDataType] - collection_types t = Puppet::Pops::Types::PArrayType.new() tested_types.each {|t2| t.should_not be_assignable_to(t2.new) } @@ -628,7 +657,7 @@ describe 'The type calculator' do it 'Hash is not assignable to any disjunct type' do tested_types = all_types - [ - Puppet::Pops::Types::PObjectType, + Puppet::Pops::Types::PAnyType, Puppet::Pops::Types::PDataType] - collection_types t = Puppet::Pops::Types::PHashType.new() tested_types.each {|t2| t.should_not be_assignable_to(t2.new) } @@ -647,7 +676,7 @@ describe 'The type calculator' do it 'Tuple is not assignable to any disjunct type' do tested_types = all_types - [ - Puppet::Pops::Types::PObjectType, + Puppet::Pops::Types::PAnyType, Puppet::Pops::Types::PDataType] - collection_types t = Puppet::Pops::Types::PTupleType.new() tested_types.each {|t2| t.should_not be_assignable_to(t2.new) } @@ -666,7 +695,7 @@ describe 'The type calculator' do it 'Struct is not assignable to any disjunct type' do tested_types = all_types - [ - Puppet::Pops::Types::PObjectType, + Puppet::Pops::Types::PAnyType, Puppet::Pops::Types::PDataType] - collection_types t = Puppet::Pops::Types::PStructType.new() tested_types.each {|t2| t.should_not be_assignable_to(t2.new) } @@ -678,7 +707,7 @@ describe 'The type calculator' do t = Puppet::Pops::Types::PCallableType.new() tested_types = all_types - [ Puppet::Pops::Types::PCallableType, - Puppet::Pops::Types::PObjectType] + Puppet::Pops::Types::PAnyType] tested_types.each {|t2| t.should_not be_assignable_to(t2.new) } end end @@ -793,9 +822,50 @@ describe 'The type calculator' do calculator.assignable?(pattern, enum).should == true end + it 'pattern should accept a variant where all variants are acceptable' do + pattern = pattern_t(/^\w+$/) + calculator.assignable?(pattern, variant_t(string_t('a'), string_t('b'))).should == true + end + + end + + context 'when dealing with enums' do + it 'should accept a string with matching content' do + calculator.assignable?(enum_t('a', 'b'), string_t('a')).should == true + calculator.assignable?(enum_t('a', 'b'), string_t('b')).should == true + calculator.assignable?(enum_t('a', 'b'), string_t('c')).should == false + end + + it 'should accept an enum with matching enum' do + calculator.assignable?(enum_t('a', 'b'), enum_t('a', 'b')).should == true + calculator.assignable?(enum_t('a', 'b'), enum_t('a')).should == true + calculator.assignable?(enum_t('a', 'b'), enum_t('c')).should == false + end + + it 'enum should accept a variant where all variants are acceptable' do + enum = enum_t('a', 'b') + calculator.assignable?(enum, variant_t(string_t('a'), string_t('b'))).should == true + end end context 'when dealing with tuples' do + it 'matches empty tuples' do + tuple1 = tuple_t() + tuple2 = tuple_t() + + calculator.assignable?(tuple1, tuple2).should == true + calculator.assignable?(tuple2, tuple1).should == true + end + + it 'accepts an empty tuple as assignable to a tuple with a min size of 0' do + tuple1 = tuple_t(Object) + factory.constrain_size(tuple1, 0, :default) + tuple2 = tuple_t() + + calculator.assignable?(tuple1, tuple2).should == true + calculator.assignable?(tuple2, tuple1).should == false + end + it 'should accept matching tuples' do tuple1 = tuple_t(1,2) tuple2 = tuple_t(Integer,Integer) @@ -859,6 +929,17 @@ describe 'The type calculator' do calculator.assignable?(tuple1, array).should == true calculator.assignable?(array, tuple1).should == true end + + it 'should accept empty array when tuple allows min of 0' do + tuple1 = tuple_t(Integer) + factory.constrain_size(tuple1, 0, 1) + + array = array_t(Integer) + factory.constrain_size(array, 0, 0) + + calculator.assignable?(tuple1, array).should == true + calculator.assignable?(array, tuple1).should == false + end end context 'when dealing with structs' do @@ -960,21 +1041,48 @@ describe 'The type calculator' do context 'when testing if x is instance of type t' do include_context "types_setup" - it 'should consider undef to be instance of Object and NilType' do + it 'should consider undef to be instance of Any, NilType, and optional' do calculator.instance?(Puppet::Pops::Types::PNilType.new(), nil).should == true - calculator.instance?(Puppet::Pops::Types::PObjectType.new(), nil).should == true + calculator.instance?(Puppet::Pops::Types::PAnyType.new(), nil).should == true + calculator.instance?(Puppet::Pops::Types::POptionalType.new(), nil).should == true end - it 'should not consider undef to be an instance of any other type than Object and NilType and Data' do - types_to_test = all_types - [ - Puppet::Pops::Types::PObjectType, + it 'all types should be (ruby) instance of PAnyType' do + all_types.each do |t| + t.new.is_a?(Puppet::Pops::Types::PAnyType).should == true + end + end + + it "should consider :undef to be instance of Runtime['ruby', 'Symbol]" do + calculator.instance?(Puppet::Pops::Types::PRuntimeType.new(:runtime => :ruby, :runtime_type_name => 'Symbol'), :undef).should == true + end + + it 'should not consider undef to be an instance of any other type than Any, NilType and Data' do + types_to_test = all_types - [ + Puppet::Pops::Types::PAnyType, Puppet::Pops::Types::PNilType, - Puppet::Pops::Types::PDataType] + Puppet::Pops::Types::PDataType, + Puppet::Pops::Types::POptionalType, + ] types_to_test.each {|t| calculator.instance?(t.new, nil).should == false } types_to_test.each {|t| calculator.instance?(t.new, :undef).should == false } end + it 'should consider default to be instance of Default and Any' do + calculator.instance?(Puppet::Pops::Types::PDefaultType.new(), :default).should == true + calculator.instance?(Puppet::Pops::Types::PAnyType.new(), :default).should == true + end + + it 'should not consider "default" to be an instance of anything but Default, and Any' do + types_to_test = all_types - [ + Puppet::Pops::Types::PAnyType, + Puppet::Pops::Types::PDefaultType, + ] + + types_to_test.each {|t| calculator.instance?(t.new, :default).should == false } + end + it 'should consider fixnum instanceof PIntegerType' do calculator.instance?(Puppet::Pops::Types::PIntegerType.new(), 1).should == true end @@ -1075,7 +1183,7 @@ describe 'The type calculator' do context 'and t is Data' do it 'undef should be considered instance of Data' do - calculator.instance?(data_t, :undef).should == true + calculator.instance?(data_t, nil).should == true end it 'other symbols should not be considered instance of Data' do @@ -1092,21 +1200,18 @@ describe 'The type calculator' do it 'a hash with nil/undef data should be considered instance of Data' do calculator.instance?(data_t, {'a' => nil}).should == true - calculator.instance?(data_t, {'a' => :undef}).should == true end - it 'a hash with nil/undef key should not considered instance of Data' do + it 'a hash with nil/default key should not considered instance of Data' do calculator.instance?(data_t, {nil => 10}).should == false - calculator.instance?(data_t, {:undef => 10}).should == false + calculator.instance?(data_t, {:default => 10}).should == false end - it 'an array with undef entries should be considered instance of Data' do - calculator.instance?(data_t, [:undef]).should == true + it 'an array with nil entries should be considered instance of Data' do calculator.instance?(data_t, [nil]).should == true end - it 'an array with undef / data entries should be considered instance of Data' do - calculator.instance?(data_t, [1, :undef, 'a']).should == true + it 'an array with nil + data entries should be considered instance of Data' do calculator.instance?(data_t, [1, nil, 'a']).should == true end end @@ -1119,10 +1224,8 @@ describe 'The type calculator' do the_block = factory.LAMBDA(params,factory.literal(42)) the_closure = Puppet::Pops::Evaluator::Closure.new(:fake_evaluator, the_block, :fake_scope) expect(calculator.instance?(all_callables_t, the_closure)).to be_true - # TODO: lambdas are currently unttypes, anything can be given if arg count is correct - expect(calculator.instance?(callable_t(optional_object_t), the_closure)).to be_true - # Arg count is wrong - expect(calculator.instance?(callable_t(optional_object_t, optional_object_t), the_closure)).to be_false + expect(calculator.instance?(callable_t(object_t), the_closure)).to be_true + expect(calculator.instance?(callable_t(object_t, object_t), the_closure)).to be_false end it 'a Function instance should be considered a Callable' do @@ -1192,8 +1295,8 @@ describe 'The type calculator' do calculator.string(Puppet::Pops::Types::PType.new()).should == 'Type' end - it 'should yield \'Object\' for PObjectType' do - calculator.string(Puppet::Pops::Types::PObjectType.new()).should == 'Object' + it 'should yield \'Object\' for PAnyType' do + calculator.string(Puppet::Pops::Types::PAnyType.new()).should == 'Any' end it 'should yield \'Scalar\' for PScalarType' do @@ -1397,20 +1500,29 @@ describe 'The type calculator' do expect(calculator.string(callable_t(String, Integer))).to eql("Callable[String, Integer]") end - it "should yield 'Callable[t,min.max]' for callable with size constraint (infinite max)" do + it "should yield 'Callable[t,min,max]' for callable with size constraint (infinite max)" do expect(calculator.string(callable_t(String, 0))).to eql("Callable[String, 0, default]") end - it "should yield 'Callable[t,min.max]' for callable with size constraint (capped max)" do + it "should yield 'Callable[t,min,max]' for callable with size constraint (capped max)" do expect(calculator.string(callable_t(String, 0, 3))).to eql("Callable[String, 0, 3]") end + it "should yield 'Callable[min,max]' callable with size > 0" do + expect(calculator.string(callable_t(0, 0))).to eql("Callable[0, 0]") + expect(calculator.string(callable_t(0, 1))).to eql("Callable[0, 1]") + expect(calculator.string(callable_t(0, :default))).to eql("Callable[0, default]") + end + it "should yield 'Callable[Callable]' for callable with block" do expect(calculator.string(callable_t(all_callables_t))).to eql("Callable[0, 0, Callable]") expect(calculator.string(callable_t(string_t, all_callables_t))).to eql("Callable[String, Callable]") expect(calculator.string(callable_t(string_t, 1,1, all_callables_t))).to eql("Callable[String, 1, 1, Callable]") end + it "should yield Unit for a Unit type" do + expect(calculator.string(unit_t)).to eql('Unit') + end end context 'when processing meta type' do @@ -1428,7 +1540,7 @@ describe 'The type calculator' do calculator.infer(Puppet::Pops::Types::PCollectionType.new()).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::PArrayType.new() ).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::PHashType.new() ).is_a?(ptype).should() == true - calculator.infer(Puppet::Pops::Types::PRubyType.new() ).is_a?(ptype).should() == true + calculator.infer(Puppet::Pops::Types::PRuntimeType.new() ).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::PHostClassType.new() ).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::PResourceType.new() ).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::PEnumType.new() ).is_a?(ptype).should() == true @@ -1453,7 +1565,7 @@ describe 'The type calculator' do calculator.string(calculator.infer(Puppet::Pops::Types::PCollectionType.new())).should == "Type[Collection]" calculator.string(calculator.infer(Puppet::Pops::Types::PArrayType.new() )).should == "Type[Array[?]]" calculator.string(calculator.infer(Puppet::Pops::Types::PHashType.new() )).should == "Type[Hash[?, ?]]" - calculator.string(calculator.infer(Puppet::Pops::Types::PRubyType.new() )).should == "Type[Ruby[?]]" + calculator.string(calculator.infer(Puppet::Pops::Types::PRuntimeType.new() )).should == "Type[Runtime[?, ?]]" calculator.string(calculator.infer(Puppet::Pops::Types::PHostClassType.new() )).should == "Type[Class]" calculator.string(calculator.infer(Puppet::Pops::Types::PResourceType.new() )).should == "Type[Resource]" calculator.string(calculator.infer(Puppet::Pops::Types::PEnumType.new() )).should == "Type[Enum]" @@ -1462,6 +1574,10 @@ describe 'The type calculator' do calculator.string(calculator.infer(Puppet::Pops::Types::PTupleType.new() )).should == "Type[Tuple]" calculator.string(calculator.infer(Puppet::Pops::Types::POptionalType.new() )).should == "Type[Optional]" calculator.string(calculator.infer(Puppet::Pops::Types::PCallableType.new() )).should == "Type[Callable]" + + calculator.infer(Puppet::Pops::Types::PResourceType.new(:type_name => 'foo::fee::fum')).to_s.should == "Type[Foo::Fee::Fum]" + calculator.string(calculator.infer(Puppet::Pops::Types::PResourceType.new(:type_name => 'foo::fee::fum'))).should == "Type[Foo::Fee::Fum]" + calculator.infer(Puppet::Pops::Types::PResourceType.new(:type_name => 'Foo::Fee::Fum')).to_s.should == "Type[Foo::Fee::Fum]" end it "computes the common type of PType's type parameter" do @@ -1584,6 +1700,87 @@ describe 'The type calculator' do end end + context 'when determening callability' do + context 'and given is exact' do + it 'with callable' do + required = callable_t(string_t) + given = callable_t(string_t) + calculator.callable?(required, given).should == true + end + + it 'with args tuple' do + required = callable_t(string_t) + given = tuple_t(string_t) + calculator.callable?(required, given).should == true + end + + it 'with args tuple having a block' do + required = callable_t(string_t, callable_t(string_t)) + given = tuple_t(string_t, callable_t(string_t)) + calculator.callable?(required, given).should == true + end + + it 'with args array' do + required = callable_t(string_t) + given = array_t(string_t) + factory.constrain_size(given, 1, 1) + calculator.callable?(required, given).should == true + end + end + + context 'and given is more generic' do + it 'with callable' do + required = callable_t(string_t) + given = callable_t(object_t) + calculator.callable?(required, given).should == true + end + + it 'with args tuple' do + required = callable_t(string_t) + given = tuple_t(object_t) + calculator.callable?(required, given).should == false + end + + it 'with args tuple having a block' do + required = callable_t(string_t, callable_t(string_t)) + given = tuple_t(string_t, callable_t(object_t)) + calculator.callable?(required, given).should == true + end + + it 'with args tuple having a block with captures rest' do + required = callable_t(string_t, callable_t(string_t)) + given = tuple_t(string_t, callable_t(object_t, 0, :default)) + calculator.callable?(required, given).should == true + end + end + + context 'and given is more specific' do + it 'with callable' do + required = callable_t(object_t) + given = callable_t(string_t) + calculator.callable?(required, given).should == false + end + + it 'with args tuple' do + required = callable_t(object_t) + given = tuple_t(string_t) + calculator.callable?(required, given).should == true + end + + it 'with args tuple having a block' do + required = callable_t(string_t, callable_t(object_t)) + given = tuple_t(string_t, callable_t(string_t)) + calculator.callable?(required, given).should == false + end + + it 'with args tuple having a block with captures rest' do + required = callable_t(string_t, callable_t(object_t)) + given = tuple_t(string_t, callable_t(string_t, 0, :default)) + calculator.callable?(required, given).should == false + end + end + end + matcher :be_assignable_to do |type| calc = Puppet::Pops::Types::TypeCalculator.new diff --git a/spec/unit/pops/types/type_factory_spec.rb b/spec/unit/pops/types/type_factory_spec.rb index a5b949640..cca19a75c 100644 --- a/spec/unit/pops/types/type_factory_spec.rb +++ b/spec/unit/pops/types/type_factory_spec.rb @@ -67,6 +67,10 @@ describe 'The type factory' do Puppet::Pops::Types::TypeFactory.undef().class().should == Puppet::Pops::Types::PNilType end + it 'default() returns PDefaultType' do + Puppet::Pops::Types::TypeFactory.default().class().should == Puppet::Pops::Types::PDefaultType + end + it 'range(to, from) returns PIntegerType' do t = Puppet::Pops::Types::TypeFactory.range(1,2) t.class().should == Puppet::Pops::Types::PIntegerType @@ -150,10 +154,11 @@ describe 'The type factory' do ht.element_type.class.should == Puppet::Pops::Types::PDataType end - it 'ruby(1) returns PRubyType[\'Fixnum\']' do + it 'ruby(1) returns PRuntimeType[ruby, \'Fixnum\']' do ht = Puppet::Pops::Types::TypeFactory.ruby(1) - ht.class().should == Puppet::Pops::Types::PRubyType - ht.ruby_class.should == 'Fixnum' + ht.class().should == Puppet::Pops::Types::PRuntimeType + ht.runtime.should == :ruby + ht.runtime_type_name.should == 'Fixnum' end it 'a size constrained collection can be created from array' do diff --git a/spec/unit/pops/types/type_parser_spec.rb b/spec/unit/pops/types/type_parser_spec.rb index 95595e55d..b67b7b6cd 100644 --- a/spec/unit/pops/types/type_parser_spec.rb +++ b/spec/unit/pops/types/type_parser_spec.rb @@ -5,7 +5,8 @@ describe Puppet::Pops::Types::TypeParser do extend RSpec::Matchers::DSL let(:parser) { Puppet::Pops::Types::TypeParser.new } - let(:types) { Puppet::Pops::Types::TypeFactory } + let(:types) { Puppet::Pops::Types::TypeFactory } + it "rejects a puppet expression" do expect { parser.parse("1 + 1") }.to raise_error(Puppet::ParseError, /The expression <1 \+ 1> is not a valid type specification/) @@ -30,7 +31,7 @@ describe Puppet::Pops::Types::TypeParser do end [ - 'Object', 'Data', 'CatalogEntry', 'Boolean', 'Scalar', 'Undef', 'Numeric', + 'Any', 'Data', 'CatalogEntry', 'Boolean', 'Scalar', 'Undef', 'Numeric', 'Default' ].each do |name| it "does not support parameterizing unparameterized type <#{name}>" do expect { parser.parse("#{name}[Integer]") }.to raise_unparameterized_error_for(name) @@ -38,7 +39,7 @@ describe Puppet::Pops::Types::TypeParser do end it "parses a simple, unparameterized type into the type object" do - expect(the_type_parsed_from(types.object)).to be_the_type(types.object) + expect(the_type_parsed_from(types.any)).to be_the_type(types.any) expect(the_type_parsed_from(types.integer)).to be_the_type(types.integer) expect(the_type_parsed_from(types.float)).to be_the_type(types.float) expect(the_type_parsed_from(types.string)).to be_the_type(types.string) @@ -50,6 +51,7 @@ describe Puppet::Pops::Types::TypeParser do expect(the_type_parsed_from(types.tuple)).to be_the_type(types.tuple) expect(the_type_parsed_from(types.struct)).to be_the_type(types.struct) expect(the_type_parsed_from(types.optional)).to be_the_type(types.optional) + expect(the_type_parsed_from(types.default)).to be_the_type(types.default) end it "interprets an unparameterized Array as an Array of Data" do @@ -113,6 +115,18 @@ describe Puppet::Pops::Types::TypeParser do expect(the_type_parsed_from(struct_t)).to be_the_type(struct_t) end + describe "handles parsing of patterns and regexp" do + { 'Pattern[/([a-z]+)([1-9]+)/]' => [:pattern, [/([a-z]+)([1-9]+)/]], + 'Pattern["([a-z]+)([1-9]+)"]' => [:pattern, [/([a-z]+)([1-9]+)/]], + 'Regexp[/([a-z]+)([1-9]+)/]' => [:regexp, [/([a-z]+)([1-9]+)/]], + 'Pattern[/x9/, /([a-z]+)([1-9]+)/]' => [:pattern, [/x9/, /([a-z]+)([1-9]+)/]], + }.each do |source, type| + it "such that the source '#{source}' yields the type #{type.to_s}" do + expect(parser.parse(source)).to be_the_type(Puppet::Pops::Types::TypeFactory.send(type[0], *type[1])) + end + end + end + it "rejects an collection spec with the wrong number of parameters" do expect { parser.parse("Array[Integer, 1,2,3]") }.to raise_the_parameter_error("Array", "1 to 3", 4) expect { parser.parse("Hash[Integer, Integer, 1,2,3]") }.to raise_the_parameter_error("Hash", "1 to 4", 5) @@ -159,7 +173,7 @@ describe Puppet::Pops::Types::TypeParser do end it 'parses a ruby type' do - expect(parser.parse("Ruby['Integer']")).to be_the_type(types.ruby_type('Integer')) + expect(parser.parse("Runtime[ruby, 'Integer']")).to be_the_type(types.ruby_type('Integer')) end it 'parses a callable type' do @@ -178,12 +192,19 @@ describe Puppet::Pops::Types::TypeParser do expect(parser.parse("Callable[String, Callable[Boolean]]")).to be_the_type(types.callable(String, types.callable(true))) end - it 'parses a parameterized callable type with only min/max' do + it 'parses a parameterized callable type with 0 min/max' do t = parser.parse("Callable[0,0]") expect(t).to be_the_type(types.callable()) expect(t.param_types.types).to be_empty end + it 'parses a parameterized callable type with >0 min/max' do + t = parser.parse("Callable[0,1]") + expect(t).to be_the_type(types.callable(0,1)) + # Contains a Unit type to indicate "called with what you accept" + expect(t.param_types.types[0]).to be_the_type(Puppet::Pops::Types::PUnitType.new()) + end + matcher :be_the_type do |type| calc = Puppet::Pops::Types::TypeCalculator.new diff --git a/spec/unit/pops/validator/validator_spec.rb b/spec/unit/pops/validator/validator_spec.rb index 1d865de5f..31defdd6b 100644 --- a/spec/unit/pops/validator/validator_spec.rb +++ b/spec/unit/pops/validator/validator_spec.rb @@ -6,12 +6,12 @@ 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 +describe "validating 4x" do include ParserRspecHelper include PuppetSpec::Pops let(:acceptor) { Puppet::Pops::Validation::Acceptor.new() } - let(:validator) { Puppet::Pops::Validation::ValidatorFactory_3_1.new().validator(acceptor) } + let(:validator) { Puppet::Pops::Validation::ValidatorFactory_4_0.new().validator(acceptor) } def validate(model) validator.validate(model) @@ -25,44 +25,160 @@ describe "validating 3x" do end it 'should raise error for illegal variable names' do - pending "validation was too strict, now too relaxed - validation missing" - 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) + expect(validate(fqn('Aaa').var())).to have_issue(Puppet::Pops::Issues::ILLEGAL_VAR_NAME) + expect(validate(fqn('AAA').var())).to have_issue(Puppet::Pops::Issues::ILLEGAL_VAR_NAME) + expect(validate(fqn('aaa::_aaa').var())).to have_issue(Puppet::Pops::Issues::ILLEGAL_VAR_NAME) end - it 'should raise error for -= assignment' do - expect(validate(fqn('aaa').minus_set(2))).to have_issue(Puppet::Pops::Issues::UNSUPPORTED_OPERATOR) + it 'should not raise error for variable name with underscore first in first name segment' do + expect(validate(fqn('_aa').var())).to_not have_issue(Puppet::Pops::Issues::ILLEGAL_VAR_NAME) + expect(validate(fqn('::_aa').var())).to_not have_issue(Puppet::Pops::Issues::ILLEGAL_VAR_NAME) end -end + context 'for non productive expressions' do + [ '1', + '3.14', + "'a'", + '"a"', + '"${$a=10}"', # interpolation with side effect + 'false', + 'true', + 'default', + 'undef', + '[1,2,3]', + '{a=>10}', + 'if 1 {2}', + 'if 1 {2} else {3}', + 'if 1 {2} elsif 3 {4}', + 'unless 1 {2}', + 'unless 1 {2} else {3}', + '1 ? 2 => 3', + '1 ? { 2 => 3}', + '-1', + '-foo()', # unary minus on productive + '1+2', + '1<2', + '(1<2)', + '!true', + '!foo()', # not on productive + '$a', + '$a[1]', + 'name', + 'Type', + 'Type[foo]' + ].each do |expr| + it "produces error for non productive: #{expr}" do + source = "#{expr}; $a = 10" + expect(validate(parse(source))).to have_issue(Puppet::Pops::Issues::IDEM_EXPRESSION_NOT_LAST) + end -describe "validating 4x" do - include ParserRspecHelper - include PuppetSpec::Pops + it "does not produce error when last for non productive: #{expr}" do + source = " $a = 10; #{expr}" + expect(validate(parse(source))).to_not have_issue(Puppet::Pops::Issues::IDEM_EXPRESSION_NOT_LAST) + end + end - let(:acceptor) { Puppet::Pops::Validation::Acceptor.new() } - let(:validator) { Puppet::Pops::Validation::ValidatorFactory_4_0.new().validator(acceptor) } + [ + 'if 1 {$a = 1}', + 'if 1 {2} else {$a=1}', + 'if 1 {2} elsif 3 {$a=1}', + 'unless 1 {$a=1}', + 'unless 1 {2} else {$a=1}', + '$a = 1 ? 2 => 3', + '$a = 1 ? { 2 => 3}', + 'Foo[a] -> Foo[b]', + '($a=1)', + 'foo()', + '$a.foo()' + ].each do |expr| - def validate(model) - validator.validate(model) - acceptor + it "does not produce error when for productive: #{expr}" do + source = "#{expr}; $x = 1" + expect(validate(parse(source))).to_not have_issue(Puppet::Pops::Issues::IDEM_EXPRESSION_NOT_LAST) + end + end + + ['class', 'define', 'node'].each do |type| + it "flags non productive expression last in #{type}" do + source = <<-SOURCE + #{type} nope { + 1 + } + end + SOURCE + expect(validate(parse(source))).to have_issue(Puppet::Pops::Issues::IDEM_NOT_ALLOWED_LAST) + end + end end - it 'should raise error for illegal names' do - pending "validation was too strict, now too relaxed - validation missing" - expect(validate(fqn('Aaa'))).to have_issue(Puppet::Pops::Issues::ILLEGAL_NAME) - expect(validate(fqn('AAA'))).to have_issue(Puppet::Pops::Issues::ILLEGAL_NAME) + context 'for reserved words' do + ['function', 'private', 'type', 'attr'].each do |word| + it "produces an error for the word '#{word}'" do + source = "$a = #{word}" + expect(validate(parse(source))).to have_issue(Puppet::Pops::Issues::RESERVED_WORD) + end + end end - it 'should raise error for illegal variable names' do - expect(validate(fqn('Aaa').var())).to have_issue(Puppet::Pops::Issues::ILLEGAL_VAR_NAME) - expect(validate(fqn('AAA').var())).to have_issue(Puppet::Pops::Issues::ILLEGAL_VAR_NAME) - expect(validate(fqn('aaa::_aaa').var())).to have_issue(Puppet::Pops::Issues::ILLEGAL_VAR_NAME) + context 'for reserved type names' do + [# type/Type, is a reserved name but results in syntax error because it is a keyword in lower case form + 'any', + 'unit', + 'scalar', + 'boolean', + 'numeric', + 'integer', + 'float', + 'collection', + 'array', + 'hash', + 'tuple', + 'struct', + 'variant', + 'optional', + 'enum', + 'regexp', + 'pattern', + 'runtime', + ].each do |name| + + it "produces an error for 'class #{name}'" do + source = "class #{name} {}" + expect(validate(parse(source))).to have_issue(Puppet::Pops::Issues::RESERVED_TYPE_NAME) + end + + it "produces an error for 'define #{name}'" do + source = "define #{name} {}" + expect(validate(parse(source))).to have_issue(Puppet::Pops::Issues::RESERVED_TYPE_NAME) + end + end end - it 'should not raise error for variable name with underscore first in first name segment' do - expect(validate(fqn('_aa').var())).to_not have_issue(Puppet::Pops::Issues::ILLEGAL_VAR_NAME) - expect(validate(fqn('::_aa').var())).to_not have_issue(Puppet::Pops::Issues::ILLEGAL_VAR_NAME) + context 'for reserved parameter names' do + ['name', 'title'].each do |word| + it "produces an error when $#{word} is used as a parameter in a class" do + source = "class x ($#{word}) {}" + expect(validate(parse(source))).to have_issue(Puppet::Pops::Issues::RESERVED_PARAMETER) + end + + it "produces an error when $#{word} is used as a parameter in a define" do + source = "define x ($#{word}) {}" + expect(validate(parse(source))).to have_issue(Puppet::Pops::Issues::RESERVED_PARAMETER) + end + end + + end + + context 'for numeric parameter names' do + ['1', '0x2', '03'].each do |word| + it "produces an error when $#{word} is used as a parameter in a class" do + source = "class x ($#{word}) {}" + expect(validate(parse(source))).to have_issue(Puppet::Pops::Issues::ILLEGAL_NUMERIC_PARAMETER) + end + end end + def parse(source) + Puppet::Pops::Parser::Parser.new().parse_string(source) + end end diff --git a/spec/unit/provider/exec/posix_spec.rb b/spec/unit/provider/exec/posix_spec.rb index 7c4982fcc..02c338b69 100755 --- a/spec/unit/provider/exec/posix_spec.rb +++ b/spec/unit/provider/exec/posix_spec.rb @@ -1,19 +1,18 @@ #! /usr/bin/env ruby require 'spec_helper' -describe Puppet::Type.type(:exec).provider(:posix) do +describe Puppet::Type.type(:exec).provider(:posix), :if => Puppet.features.posix? do include PuppetSpec::Files def make_exe cmdpath = tmpdir('cmdpath') exepath = tmpfile('my_command', cmdpath) - exepath = exepath + ".exe" if Puppet.features.microsoft_windows? FileUtils.touch(exepath) File.chmod(0755, exepath) exepath end - let(:resource) { Puppet::Type.type(:exec).new(:title => File.expand_path('/foo'), :provider => :posix) } + let(:resource) { Puppet::Type.type(:exec).new(:title => '/foo', :provider => :posix) } let(:provider) { described_class.new(resource) } describe "#validatecmd" do @@ -31,7 +30,7 @@ describe Puppet::Type.type(:exec).provider(:posix) do it "should pass if command is fully qualifed" do provider.resource[:path] = ['/bogus/bin'] - provider.validatecmd(File.expand_path("/bin/blah/foo")) + provider.validatecmd("/bin/blah/foo") end end @@ -64,7 +63,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) }.returns(Puppet::Util::Execution::ProcessOutput.new('', 0)) + Puppet::Util::Execution.expects(:execute).with(filename, instance_of(Hash)).returns(Puppet::Util::Execution::ProcessOutput.new('', 0)) provider.run(filename) end @@ -91,17 +90,28 @@ describe Puppet::Type.type(:exec).provider(:posix) do expect { provider.run("cd ..") }.to raise_error(ArgumentError, "Could not find command 'cd'") end + it "does not override the user when it is already the requested user" do + Etc.stubs(:getpwuid).returns(Struct::Passwd.new('testing')) + provider.resource[:user] = 'testing' + command = make_exe + + Puppet::Util::Execution.expects(:execute).with(anything(), has_entry(:uid, nil)).returns(Puppet::Util::Execution::ProcessOutput.new('', 0)) + + provider.run(command) + end + it "should execute the command if the command given includes arguments or subcommands" 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) }.returns(Puppet::Util::Execution::ProcessOutput.new('', 0)) + Puppet::Util::Execution.expects(:execute).with("#{command} bar --sillyarg=true --blah", instance_of(Hash)).returns(Puppet::Util::Execution::ProcessOutput.new('', 0)) + provider.run("#{command} bar --sillyarg=true --blah") end it "should fail if quoted command doesn't exist" do provider.resource[:path] = ['/bogus/bin'] - command = "#{File.expand_path('/foo')} bar --sillyarg=true --blah" + command = "/foo bar --sillyarg=true --blah" expect { provider.run(%Q["#{command}"]) }.to raise_error(ArgumentError, "Could not find command '#{command}'") end @@ -110,8 +120,10 @@ 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) }.returns(Puppet::Util::Execution::ProcessOutput.new('', 0)) + Puppet::Util::Execution.expects(:execute).with(command, instance_of(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 @@ -121,7 +133,7 @@ describe Puppet::Type.type(:exec).provider(:posix) do provider.run(provider.resource[:command]) end - describe "posix locale settings", :unless => Puppet.features.microsoft_windows? do + describe "posix locale settings" do # a sentinel value that we can use to emulate what locale environment variables might be set to on an international # system. lang_sentinel_value = "en_US.UTF-8" @@ -160,7 +172,7 @@ describe Puppet::Type.type(:exec).provider(:posix) do end end - describe "posix user-related environment vars", :unless => Puppet.features.microsoft_windows? do + describe "posix user-related environment vars" do # a temporary hash that contains sentinel values for each of the user-related environment variables that we # are expected to unset during an "exec" user_sentinel_env = {} @@ -202,10 +214,6 @@ describe Puppet::Type.type(:exec).provider(:posix) do output.strip.should == sentinel_value end end - - end - - end end diff --git a/spec/unit/provider/exec/shell_spec.rb b/spec/unit/provider/exec/shell_spec.rb index 0f0faa594..932f46b6a 100755 --- a/spec/unit/provider/exec/shell_spec.rb +++ b/spec/unit/provider/exec/shell_spec.rb @@ -2,8 +2,8 @@ require 'spec_helper' describe Puppet::Type.type(:exec).provider(:shell), :unless => Puppet.features.microsoft_windows? do - let :resource do Puppet::Resource.new(:exec, 'foo') end - let :provider do described_class.new(resource) end + let(:resource) { Puppet::Type.type(:exec).new(:title => 'foo', :provider => 'shell') } + let(:provider) { described_class.new(resource) } describe "#run" do it "should be able to run builtin shell commands" do diff --git a/spec/unit/provider/file/windows_spec.rb b/spec/unit/provider/file/windows_spec.rb index a0e9d3a4e..f6d7ef0e7 100755 --- a/spec/unit/provider/file/windows_spec.rb +++ b/spec/unit/provider/file/windows_spec.rb @@ -49,22 +49,22 @@ describe Puppet::Type.type(:file).provider(:windows), :if => Puppet.features.mic describe "#id2name" do it "should return the name of the user identified by the sid" do - Puppet::Util::Windows::Security.expects(:valid_sid?).with(sid).returns(true) - Puppet::Util::Windows::Security.expects(:sid_to_name).with(sid).returns(account) + Puppet::Util::Windows::SID.expects(:valid_sid?).with(sid).returns(true) + Puppet::Util::Windows::SID.expects(:sid_to_name).with(sid).returns(account) provider.id2name(sid).should == account end it "should return the argument if it's already a name" do - Puppet::Util::Windows::Security.expects(:valid_sid?).with(account).returns(false) - Puppet::Util::Windows::Security.expects(:sid_to_name).never + Puppet::Util::Windows::SID.expects(:valid_sid?).with(account).returns(false) + Puppet::Util::Windows::SID.expects(:sid_to_name).never provider.id2name(account).should == account end it "should return nil if the user doesn't exist" do - Puppet::Util::Windows::Security.expects(:valid_sid?).with(sid).returns(true) - Puppet::Util::Windows::Security.expects(:sid_to_name).with(sid).returns(nil) + Puppet::Util::Windows::SID.expects(:valid_sid?).with(sid).returns(true) + Puppet::Util::Windows::SID.expects(:sid_to_name).with(sid).returns(nil) provider.id2name(sid).should == nil end @@ -72,7 +72,7 @@ describe Puppet::Type.type(:file).provider(:windows), :if => Puppet.features.mic describe "#name2id" do it "should delegate to name_to_sid" do - Puppet::Util::Windows::Security.expects(:name_to_sid).with(account).returns(sid) + Puppet::Util::Windows::SID.expects(:name_to_sid).with(account).returns(sid) provider.name2id(account).should == sid end diff --git a/spec/unit/provider/group/windows_adsi_spec.rb b/spec/unit/provider/group/windows_adsi_spec.rb index a7de859da..7c9366f72 100644 --- a/spec/unit/provider/group/windows_adsi_spec.rb +++ b/spec/unit/provider/group/windows_adsi_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -describe Puppet::Type.type(:group).provider(:windows_adsi) do +describe Puppet::Type.type(:group).provider(:windows_adsi), :if => Puppet.features.microsoft_windows? do let(:resource) do Puppet::Type.type(:group).new( :title => 'testers', @@ -15,8 +15,8 @@ describe Puppet::Type.type(:group).provider(:windows_adsi) do let(:connection) { stub 'connection' } before :each do - Puppet::Util::ADSI.stubs(:computer_name).returns('testcomputername') - Puppet::Util::ADSI.stubs(:connect).returns connection + Puppet::Util::Windows::ADSI.stubs(:computer_name).returns('testcomputername') + Puppet::Util::Windows::ADSI.stubs(:connect).returns connection end describe ".instances" do @@ -30,14 +30,14 @@ describe Puppet::Type.type(:group).provider(:windows_adsi) do end end - describe "group type :members property helpers", :if => Puppet.features.microsoft_windows? do + describe "group type :members property helpers" do let(:user1) { stub(:account => 'user1', :domain => '.', :to_s => 'user1sid') } let(:user2) { stub(:account => 'user2', :domain => '.', :to_s => 'user2sid') } 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) + Puppet::Util::Windows::SID.stubs(:name_to_sid_object).with('user1').returns(user1) + Puppet::Util::Windows::SID.stubs(:name_to_sid_object).with('user2').returns(user2) end describe "#members_insync?" do @@ -89,7 +89,7 @@ describe Puppet::Type.type(:group).provider(:windows_adsi) do provider.members.should =~ ['user1', 'user2', 'user3'] end - it "should be able to set group members", :if => Puppet.features.microsoft_windows? do + it "should be able to set group members" do provider.group.stubs(:members).returns ['user1', 'user2'] member_sids = [ @@ -100,8 +100,8 @@ describe Puppet::Type.type(:group).provider(:windows_adsi) do 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]) + Puppet::Util::Windows::SID.expects(:name_to_sid_object).with('user2').returns(member_sids[1]) + Puppet::Util::Windows::SID.expects(:name_to_sid_object).with('user3').returns(member_sids[2]) provider.group.expects(:remove_member_sids).with(member_sids[0]) provider.group.expects(:add_member_sids).with(member_sids[2]) @@ -115,7 +115,7 @@ describe Puppet::Type.type(:group).provider(:windows_adsi) do resource[:members] = ['user1', 'user2'] group = stub 'group' - Puppet::Util::ADSI::Group.expects(:create).with('testers').returns group + Puppet::Util::Windows::ADSI::Group.expects(:create).with('testers').returns group create = sequence('create') group.expects(:commit).in_sequence(create) @@ -125,7 +125,7 @@ describe Puppet::Type.type(:group).provider(:windows_adsi) do end it 'should not create a group if a user by the same name exists' do - Puppet::Util::ADSI::Group.expects(:create).with('testers').raises( Puppet::Error.new("Cannot create group if user 'testers' exists.") ) + Puppet::Util::Windows::ADSI::Group.expects(:create).with('testers').raises( Puppet::Error.new("Cannot create group if user 'testers' exists.") ) expect{ provider.create }.to raise_error( Puppet::Error, /Cannot create group if user 'testers' exists./ ) end @@ -138,11 +138,11 @@ describe Puppet::Type.type(:group).provider(:windows_adsi) do end it "should be able to test whether a group exists" do - Puppet::Util::ADSI.stubs(:sid_uri_safe).returns(nil) - Puppet::Util::ADSI.stubs(:connect).returns stub('connection') + Puppet::Util::Windows::ADSI.stubs(:sid_uri_safe).returns(nil) + Puppet::Util::Windows::ADSI.stubs(:connect).returns stub('connection') provider.should be_exists - Puppet::Util::ADSI.stubs(:connect).returns nil + Puppet::Util::Windows::ADSI.stubs(:connect).returns nil provider.should_not be_exists end @@ -152,8 +152,8 @@ describe Puppet::Type.type(:group).provider(:windows_adsi) do provider.delete end - it "should report the group's SID as gid", :if => Puppet.features.microsoft_windows? do - Puppet::Util::Windows::Security.expects(:name_to_sid).with('testers').returns('S-1-5-32-547') + it "should report the group's SID as gid" do + Puppet::Util::Windows::SID.expects(:name_to_sid).with('testers').returns('S-1-5-32-547') provider.gid.should == 'S-1-5-32-547' end @@ -162,7 +162,7 @@ describe Puppet::Type.type(:group).provider(:windows_adsi) do provider.send(:gid=, 500) end - it "should prefer the domain component from the resolved SID", :if => Puppet.features.microsoft_windows? do + it "should prefer the domain component from the resolved SID" do provider.members_to_s(['.\Administrators']).should == 'BUILTIN\Administrators' end end diff --git a/spec/unit/provider/package/gem_spec.rb b/spec/unit/provider/package/gem_spec.rb index f382335cd..23aa2798d 100755 --- a/spec/unit/provider/package/gem_spec.rb +++ b/spec/unit/provider/package/gem_spec.rb @@ -174,4 +174,14 @@ describe provider_class do {:provider=>:gem, :ensure=>["1.11.3.3"], :name=>"rvm"}] end end + + describe "listing gems" do + describe "searching for a single package" do + it "searches for an exact match" do + provider_class.expects(:execute).with(includes('^bundler$')).returns(File.read(my_fixture('gem-list-single-package'))) + expected = {:name => 'bundler', :ensure => %w[1.6.2], :provider => :gem} + expect(provider_class.gemlist({:justme => 'bundler'})).to eq(expected) + end + end + end end diff --git a/spec/unit/provider/package/openbsd_spec.rb b/spec/unit/provider/package/openbsd_spec.rb index 8d4f079fe..712f9cfda 100755 --- a/spec/unit/provider/package/openbsd_spec.rb +++ b/spec/unit/provider/package/openbsd_spec.rb @@ -17,7 +17,7 @@ describe provider_class do def expect_pkgadd_with_source(source) provider.expects(:pkgadd).with do |fullname| ENV.should_not be_key('PKG_PATH') - fullname.should == source + fullname.should == [source] end end @@ -28,7 +28,7 @@ describe provider_class do ENV.should be_key('PKG_PATH') ENV['PKG_PATH'].should == source - fullname.should == provider.resource[:name] + fullname.should == [provider.resource[:name]] end provider.expects(:execpipe).with(['/bin/pkg_info', '-I', provider.resource[:name]]).yields('') @@ -37,6 +37,15 @@ describe provider_class do ENV.should_not be_key('PKG_PATH') end + describe 'provider features' do + it { should be_installable } + it { should be_install_options } + it { should be_uninstallable } + it { should be_uninstall_options } + it { should be_upgradeable } + it { should be_versionable } + end + before :each do # Stub some provider methods to avoid needing the actual software # installed, so we can test on whatever platform we want. @@ -45,7 +54,7 @@ describe provider_class do provider_class.stubs(:command).with(:pkgdelete).returns('/bin/pkg_delete') end - context "::instances" do + context "#instances" do it "should return nil if execution failed" do provider_class.expects(:execpipe).raises(Puppet::ExecutionFailure, 'wawawa') provider_class.instances.should be_nil @@ -69,7 +78,7 @@ describe provider_class do instances = provider_class.instances.map {|p| {:name => p.get(:name), :ensure => p.get(:ensure), :flavor => p.get(:flavor)}} instances.size.should == 2 - instances[0].should == {:name => 'bash', :ensure => '3.1.17', :flavor => 'static'} + instances[0].should == {:name => 'bash', :ensure => '3.1.17', :flavor => 'static'} instances[1].should == {:name => 'vim', :ensure => '7.0.42', :flavor => 'no_x11'} end end @@ -100,7 +109,7 @@ describe provider_class do end it "should install correctly when given a directory-unlike source" do - source = '/whatever.pkg' + source = '/whatever.tgz' provider.resource[:source] = source expect_pkgadd_with_source(source) @@ -224,6 +233,46 @@ describe provider_class do }.to raise_error(Puppet::Error, /No valid installpath found in \/etc\/pkg\.conf and no source was set/) end end + + it 'should use install_options as Array' do + provider.resource[:source] = '/tma1/' + provider.resource[:install_options] = ['-r', '-z'] + provider.expects(:pkgadd).with(['-r', '-z', 'bash']) + provider.install + end + end + + context "#latest" do + before do + provider.resource[:source] = '/tmp/tcsh.tgz' + provider.resource[:name] = 'tcsh' + provider.stubs(:pkginfo).with('tcsh') + end + + it "should return the ensure value if the package is already installed" do + provider.stubs(:properties).returns({:ensure => '4.2.45'}) + provider.stubs(:pkginfo).with('-Q', 'tcsh') + provider.latest.should == '4.2.45' + end + + it "should recognize a new version" do + pkginfo_query = 'tcsh-6.18.01p1' + provider.stubs(:pkginfo).with('-Q', 'tcsh').returns(pkginfo_query) + provider.latest.should == '6.18.01p1' + end + + it "should recognize a newer version" do + provider.stubs(:properties).returns({:ensure => '1.6.8'}) + pkginfo_query = 'tcsh-1.6.10' + provider.stubs(:pkginfo).with('-Q', 'tcsh').returns(pkginfo_query) + provider.latest.should == '1.6.10' + end + + it "should recognize a package that is already the newest" do + pkginfo_query = 'tcsh-6.18.01p0 (installed)' + provider.stubs(:pkginfo).with('-Q', 'tcsh').returns(pkginfo_query) + provider.latest.should == '6.18.01p0' + end end context "#get_version" do @@ -233,8 +282,8 @@ describe provider_class do end it "should return the package version if in the output" do - fixture = File.read(my_fixture('pkginfo.list')) - provider.expects(:execpipe).with(%w{/bin/pkg_info -I bash}).yields(fixture) + output = 'bash-3.1.17 GNU Bourne Again Shell' + provider.expects(:execpipe).with(%w{/bin/pkg_info -I bash}).yields(output) provider.get_version.should == '3.1.17' end @@ -279,7 +328,7 @@ describe provider_class do provider.install_options.should == ['-Darch=vax'] end end - + context "#uninstall_options" do it "should return nill by default" do provider.uninstall_options.should be_nil @@ -300,7 +349,7 @@ describe provider_class do provider.uninstall_options.should == ['-Dbaddepend=1'] end end - + context "#uninstall" do describe 'when uninstalling' do it 'should use erase to purge' do @@ -308,5 +357,13 @@ describe provider_class do provider.purge end end + + describe 'with uninstall_options' do + it 'should use uninstall_options as Array' do + provider.resource[:uninstall_options] = ['-q', '-c'] + provider.expects(:pkgdelete).with(['-q', '-c'], 'bash') + provider.uninstall + end + end end end diff --git a/spec/unit/provider/package/pacman_spec.rb b/spec/unit/provider/package/pacman_spec.rb index 789fd88fb..aca7c5d64 100755 --- a/spec/unit/provider/package/pacman_spec.rb +++ b/spec/unit/provider/package/pacman_spec.rb @@ -2,58 +2,73 @@ require 'spec_helper' require 'stringio' -provider = Puppet::Type.type(:package).provider(:pacman) -describe provider do +describe Puppet::Type.type(:package).provider(:pacman) do let(:no_extra_options) { { :failonfail => true, :combine => true, :custom_environment => {} } } let(:executor) { Puppet::Util::Execution } let(:resolver) { Puppet::Util } + let(:resource) { Puppet::Type.type(:package).new(:name => 'package', :provider => 'pacman') } + let(:provider) { described_class.new(resource) } + before do resolver.stubs(:which).with('/usr/bin/pacman').returns('/usr/bin/pacman') - provider.stubs(:which).with('/usr/bin/pacman').returns('/usr/bin/pacman') + described_class.stubs(:which).with('/usr/bin/pacman').returns('/usr/bin/pacman') resolver.stubs(:which).with('/usr/bin/yaourt').returns('/usr/bin/yaourt') - provider.stubs(:which).with('/usr/bin/yaourt').returns('/usr/bin/yaourt') - @resource = Puppet::Type.type(:package).new(:name => 'package') - @provider = provider.new(@resource) + described_class.stubs(:which).with('/usr/bin/yaourt').returns('/usr/bin/yaourt') end describe "when installing" do before do - @provider.stubs(:query).returns({ + provider.stubs(:query).returns({ :ensure => '1.0' }) end - it "should call pacman to install the right package quietly" do - - if @provider.yaourt? - args = ['/usr/bin/yaourt', '--noconfirm', '-S', @resource[:name]] - else - args = ['/usr/bin/pacman', '--noconfirm', '--noprogressbar', '-Sy', @resource[:name]] - end - - executor. - expects(:execute). - at_least_once. - with(args, no_extra_options). - returns '' + it "should call pacman to install the right package quietly when yaourt is not installed" do + provider.stubs(:yaourt?).returns(false) + args = ['--noconfirm', '--noprogressbar', '-Sy', resource[:name]] + provider.expects(:pacman).at_least_once.with(*args).returns '' + provider.install + end - @provider.install + it "should call yaourt to install the right package quietly when yaourt is installed" do + provider.stubs(:yaourt?).returns(true) + args = ['--noconfirm', '-S', resource[:name]] + provider.expects(:yaourt).at_least_once.with(*args).returns '' + provider.install end it "should raise an ExecutionFailure if the installation failed" do executor.stubs(:execute).returns("") - @provider.expects(:query).returns(nil) + provider.expects(:query).returns(nil) - lambda { @provider.install }.should raise_exception(Puppet::ExecutionFailure) + lambda { provider.install }.should raise_exception(Puppet::ExecutionFailure) end - context "when :source is specified" do - before :each do - @install = sequence("install") + describe "and install_options are given" do + before do + resource[:install_options] = ['-x', {'--arg' => 'value'}] + end + + it "should call pacman to install the right package quietly when yaourt is not installed" do + provider.stubs(:yaourt?).returns(false) + args = ['--noconfirm', '--noprogressbar', '-x', '--arg=value', '-Sy', resource[:name]] + provider.expects(:pacman).at_least_once.with(*args).returns '' + provider.install end + it "should call yaourt to install the right package quietly when yaourt is installed" do + provider.stubs(:yaourt?).returns(true) + args = ['--noconfirm', '-x', '--arg=value', '-S', resource[:name]] + provider.expects(:yaourt).at_least_once.with(*args).returns '' + provider.install + end + end + + context "when :source is specified" do + let(:install_seq) { sequence("install") } + context "recognizable by pacman" do %w{ /some/package/file @@ -61,28 +76,28 @@ describe provider do ftp://some.package.in/the/air }.each do |source| it "should install #{source} directly" do - @resource[:source] = source + resource[:source] = source executor.expects(:execute). with(all_of(includes("-Sy"), includes("--noprogressbar")), no_extra_options). - in_sequence(@install). + in_sequence(install_seq). returns("") executor.expects(:execute). with(all_of(includes("-U"), includes(source)), no_extra_options). - in_sequence(@install). + in_sequence(install_seq). returns("") - @provider.install + provider.install end end end context "as a file:// URL" do + let(:actual_file_path) { "/some/package/file" } + before do - @package_file = "file:///some/package/file" - @actual_file_path = "/some/package/file" - @resource[:source] = @package_file + resource[:source] = "file:///some/package/file" end it "should install from the path segment of the URL" do @@ -91,35 +106,35 @@ describe provider do includes("--noprogressbar"), includes("--noconfirm")), no_extra_options). - in_sequence(@install). + in_sequence(install_seq). returns("") executor.expects(:execute). - with(all_of(includes("-U"), includes(@actual_file_path)), no_extra_options). - in_sequence(@install). + with(all_of(includes("-U"), includes(actual_file_path)), no_extra_options). + in_sequence(install_seq). returns("") - @provider.install + provider.install end end context "as a puppet URL" do before do - @resource[:source] = "puppet://server/whatever" + resource[:source] = "puppet://server/whatever" end it "should fail" do - lambda { @provider.install }.should raise_error(Puppet::Error) + lambda { provider.install }.should raise_error(Puppet::Error) end end context "as a malformed URL" do before do - @resource[:source] = "blah://" + resource[:source] = "blah://" end it "should fail" do - lambda { @provider.install }.should raise_error(Puppet::Error) + lambda { provider.install }.should raise_error(Puppet::Error) end end end @@ -127,19 +142,23 @@ describe provider do describe "when updating" do it "should call install" do - @provider.expects(:install).returns("install return value") - @provider.update.should == "install return value" + provider.expects(:install).returns("install return value") + provider.update.should == "install return value" end end describe "when uninstalling" do it "should call pacman to remove the right package quietly" do - executor. - expects(:execute). - with(["/usr/bin/pacman", "--noconfirm", "--noprogressbar", "-R", @resource[:name]], no_extra_options). - returns "" + args = ["/usr/bin/pacman", "--noconfirm", "--noprogressbar", "-R", resource[:name]] + executor.expects(:execute).with(args, no_extra_options).returns "" + provider.uninstall + end - @provider.uninstall + it "adds any uninstall_options" do + resource[:uninstall_options] = ['-x', {'--arg' => 'value'}] + args = ["/usr/bin/pacman", "--noconfirm", "--noprogressbar", "-x", "--arg=value", "-R", resource[:name]] + executor.expects(:execute).with(args, no_extra_options).returns "" + provider.uninstall end end @@ -147,8 +166,8 @@ describe provider do it "should query pacman" do executor. expects(:execute). - with(["/usr/bin/pacman", "-Qi", @resource[:name]], no_extra_options) - @provider.query + with(["/usr/bin/pacman", "-Qi", resource[:name]], no_extra_options) + provider.query end it "should return the version" do @@ -176,20 +195,20 @@ Description : A library-based package manager with dependency support EOF executor.expects(:execute).returns(query_output) - @provider.query.should == {:ensure => "1.01.3-2"} + provider.query.should == {:ensure => "1.01.3-2"} end it "should return a nil if the package isn't found" do executor.expects(:execute).returns("") - @provider.query.should be_nil + provider.query.should be_nil end it "should return a hash indicating that the package is missing on error" do executor.expects(:execute).raises(Puppet::ExecutionFailure.new("ERROR!")) - @provider.query.should == { + provider.query.should == { :ensure => :purged, :status => 'missing', - :name => @resource[:name], + :name => resource[:name], :error => 'ok', } end @@ -199,18 +218,18 @@ EOF describe "when fetching a package list" do it "should retrieve installed packages" do - provider.expects(:execpipe).with(["/usr/bin/pacman", '-Q']) - provider.installedpkgs + described_class.expects(:execpipe).with(["/usr/bin/pacman", '-Q']) + described_class.installedpkgs end it "should retrieve installed package groups" do - provider.expects(:execpipe).with(["/usr/bin/pacman", '-Qg']) - provider.installedgroups + described_class.expects(:execpipe).with(["/usr/bin/pacman", '-Qg']) + described_class.installedgroups end it "should return installed packages with their versions" do - provider.expects(:execpipe).yields(StringIO.new("package1 1.23-4\npackage2 2.00\n")) - packages = provider.installedpkgs + described_class.expects(:execpipe).yields(StringIO.new("package1 1.23-4\npackage2 2.00\n")) + packages = described_class.installedpkgs packages.length.should == 2 @@ -228,8 +247,8 @@ EOF end it "should return installed groups with a dummy version" do - provider.expects(:execpipe).yields(StringIO.new("group1 pkg1\ngroup1 pkg2")) - groups = provider.installedgroups + described_class.expects(:execpipe).yields(StringIO.new("group1 pkg1\ngroup1 pkg2")) + groups = described_class.installedgroups groups.length.should == 1 @@ -241,14 +260,14 @@ EOF end it "should return nil on error" do - provider.expects(:execpipe).twice.raises(Puppet::ExecutionFailure.new("ERROR!")) - provider.instances.should be_nil + described_class.expects(:execpipe).twice.raises(Puppet::ExecutionFailure.new("ERROR!")) + described_class.instances.should be_nil end it "should warn on invalid input" do - provider.expects(:execpipe).yields(StringIO.new("blah")) - provider.expects(:warning).with("Failed to match line blah") - provider.installedpkgs == [] + described_class.expects(:execpipe).yields(StringIO.new("blah")) + described_class.expects(:warning).with("Failed to match line blah") + described_class.installedpkgs == [] end end @@ -265,7 +284,7 @@ EOF in_sequence(get_latest_version). returns("") - @provider.latest + provider.latest end it "should get query pacman for the latest version" do @@ -277,10 +296,10 @@ EOF executor. expects(:execute). in_sequence(get_latest_version). - with(['/usr/bin/pacman', '-Sp', '--print-format', '%v', @resource[:name]], no_extra_options). + with(['/usr/bin/pacman', '-Sp', '--print-format', '%v', resource[:name]], no_extra_options). returns("") - @provider.latest + provider.latest end it "should return the version number from pacman" do @@ -289,7 +308,7 @@ EOF at_least_once(). returns("1.00.2-3\n") - @provider.latest.should == "1.00.2-3" + provider.latest.should == "1.00.2-3" end end end diff --git a/spec/unit/provider/package/windows/package_spec.rb b/spec/unit/provider/package/windows/package_spec.rb index 7466be1e9..632fa13a6 100755 --- a/spec/unit/provider/package/windows/package_spec.rb +++ b/spec/unit/provider/package/windows/package_spec.rb @@ -32,7 +32,7 @@ describe Puppet::Provider::Package::Windows::Package do end end - context '::with_key' do + context '::with_key', :if => Puppet.features.microsoft_windows? do it 'should search HKLM (64 & 32) and HKCU (64 & 32)' do seq = sequence('reg') @@ -44,8 +44,8 @@ describe Puppet::Provider::Package::Windows::Package do subject.with_key { |key, values| } end - it 'should ignore file not found exceptions', :if => Puppet.features.microsoft_windows? do - ex = Puppet::Util::Windows::Error.new('Failed to open registry key', Windows::Error::ERROR_FILE_NOT_FOUND) + it 'should ignore file not found exceptions' do + ex = Puppet::Util::Windows::Error.new('Failed to open registry key', Puppet::Util::Windows::Error::ERROR_FILE_NOT_FOUND) # make sure we don't stop after the first exception subject.expects(:open).times(4).raises(ex) @@ -55,13 +55,13 @@ describe Puppet::Provider::Package::Windows::Package do keys.should be_empty end - it 'should raise other types of exceptions', :if => Puppet.features.microsoft_windows? do - ex = Puppet::Util::Windows::Error.new('Failed to open registry key', Windows::Error::ERROR_ACCESS_DENIED) + it 'should raise other types of exceptions' do + ex = Puppet::Util::Windows::Error.new('Failed to open registry key', Puppet::Util::Windows::Error::ERROR_ACCESS_DENIED) subject.expects(:open).raises(ex) expect { subject.with_key{ |key, values| } - }.to raise_error(Puppet::Error, /Access is denied/) + }.to raise_error(Puppet::Util::Windows::Error, /Access is denied/) end end @@ -103,6 +103,21 @@ describe Puppet::Provider::Package::Windows::Package do end end + context '::munge' do + it 'should shell quote strings with spaces and fix forward slashes' do + subject.munge('c:/windows/the thing').should == '"c:\windows\the thing"' + end + it 'should leave properly formatted paths alone' do + subject.munge('c:\windows\thething').should == 'c:\windows\thething' + end + end + + context '::replace_forward_slashes' do + it 'should replace forward with back slashes' do + subject.replace_forward_slashes('c:/windows/thing/stuff').should == 'c:\windows\thing\stuff' + end + end + context '::quote' do it 'should shell quote strings with spaces' do subject.quote('foo bar').should == '"foo bar"' diff --git a/spec/unit/provider/package/yum_spec.rb b/spec/unit/provider/package/yum_spec.rb index d7130ee77..20a523be9 100755 --- a/spec/unit/provider/package/yum_spec.rb +++ b/spec/unit/provider/package/yum_spec.rb @@ -81,6 +81,7 @@ describe provider_class do resource[:ensure] = :installed resource[:install_options] = ['-t', {'-x' => 'expackage'}] + provider.expects(:yum).with('-d', '0', '-e', '0', '-y', ['-t', '-x=expackage'], :list, name) provider.expects(:yum).with('-d', '0', '-e', '0', '-y', ['-t', '-x=expackage'], :install, name) provider.install end diff --git a/spec/unit/provider/parsedfile_spec.rb b/spec/unit/provider/parsedfile_spec.rb index f8a1773de..b814bc7ee 100755 --- a/spec/unit/provider/parsedfile_spec.rb +++ b/spec/unit/provider/parsedfile_spec.rb @@ -108,7 +108,7 @@ describe Puppet::Provider::ParsedFile do provider.prefetch @filetype = Puppet::Util::FileType.filetype(:flat).new("/my/file") - Puppet::Util::FileType.filetype(:flat).stubs(:new).with("/my/file").returns @filetype + Puppet::Util::FileType.filetype(:flat).stubs(:new).with("/my/file",nil).returns @filetype @filetype.stubs(:write) end diff --git a/spec/unit/provider/scheduled_task/win32_taskscheduler_spec.rb b/spec/unit/provider/scheduled_task/win32_taskscheduler_spec.rb index 4a950dad3..3d37956c5 100644 --- a/spec/unit/provider/scheduled_task/win32_taskscheduler_spec.rb +++ b/spec/unit/provider/scheduled_task/win32_taskscheduler_spec.rb @@ -1,7 +1,7 @@ #! /usr/bin/env ruby require 'spec_helper' -require 'win32/taskscheduler' if Puppet.features.microsoft_windows? +require 'puppet/util/windows/taskscheduler' if Puppet.features.microsoft_windows? shared_examples_for "a trigger that handles start_date and start_time" do let(:trigger) do @@ -569,27 +569,27 @@ describe Puppet::Type.type(:scheduled_task).provider(:win32_taskscheduler), :if let(:resource) { described_class.new(:name => 'foobar', :command => 'C:\Windows\System32\notepad.exe') } it 'should consider the user as in sync if the name matches' do - Puppet::Util::Windows::Security.expects(:name_to_sid).with('joe').twice.returns('SID A') + Puppet::Util::Windows::SID.expects(:name_to_sid).with('joe').twice.returns('SID A') resource.should be_user_insync('joe', ['joe']) end it 'should consider the user as in sync if the current user is fully qualified' do - Puppet::Util::Windows::Security.expects(:name_to_sid).with('joe').returns('SID A') - Puppet::Util::Windows::Security.expects(:name_to_sid).with('MACHINE\joe').returns('SID A') + Puppet::Util::Windows::SID.expects(:name_to_sid).with('joe').returns('SID A') + Puppet::Util::Windows::SID.expects(:name_to_sid).with('MACHINE\joe').returns('SID A') resource.should be_user_insync('MACHINE\joe', ['joe']) end it 'should consider a current user of the empty string to be the same as the system user' do - Puppet::Util::Windows::Security.expects(:name_to_sid).with('system').twice.returns('SYSTEM SID') + Puppet::Util::Windows::SID.expects(:name_to_sid).with('system').twice.returns('SYSTEM SID') resource.should be_user_insync('', ['system']) end it 'should consider different users as being different' do - Puppet::Util::Windows::Security.expects(:name_to_sid).with('joe').returns('SID A') - Puppet::Util::Windows::Security.expects(:name_to_sid).with('bob').returns('SID B') + Puppet::Util::Windows::SID.expects(:name_to_sid).with('joe').returns('SID A') + Puppet::Util::Windows::SID.expects(:name_to_sid).with('bob').returns('SID B') resource.should_not be_user_insync('joe', ['bob']) end @@ -1469,7 +1469,7 @@ describe Puppet::Type.type(:scheduled_task).provider(:win32_taskscheduler), :if end it 'should use nil for user and password when setting the user to the SYSTEM account' do - Puppet::Util::Windows::Security.stubs(:name_to_sid).with('system').returns('SYSTEM SID') + Puppet::Util::Windows::SID.stubs(:name_to_sid).with('system').returns('SYSTEM SID') resource = Puppet::Type.type(:scheduled_task).new( :name => 'Test Task', @@ -1483,7 +1483,7 @@ describe Puppet::Type.type(:scheduled_task).provider(:win32_taskscheduler), :if end it 'should use the specified user and password when setting the user to anything other than SYSTEM' do - Puppet::Util::Windows::Security.stubs(:name_to_sid).with('my_user_name').returns('SID A') + Puppet::Util::Windows::SID.stubs(:name_to_sid).with('my_user_name').returns('SID A') resource = Puppet::Type.type(:scheduled_task).new( :name => 'Test Task', diff --git a/spec/unit/provider/service/openbsd_spec.rb b/spec/unit/provider/service/openbsd_spec.rb index e7ba4a4db..ad1e50d18 100644..100755 --- a/spec/unit/provider/service/openbsd_spec.rb +++ b/spec/unit/provider/service/openbsd_spec.rb @@ -173,13 +173,13 @@ describe provider_class do it "can append to the package_scripts array and return the result" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'cupsd')) provider.expects(:load_rcconf_local_array).returns ['pkg_scripts="dbus_daemon"'] - expect(provider.pkg_scripts_append).to match_array(['dbus_daemon', 'cupsd']) + provider.pkg_scripts_append.should === ['dbus_daemon', 'cupsd'] end it "should not duplicate the script name" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'cupsd')) provider.expects(:load_rcconf_local_array).returns ['pkg_scripts="cupsd dbus_daemon"'] - expect(provider.pkg_scripts_append).to match_array(['dbus_daemon', 'cupsd']) + provider.pkg_scripts_append.should === ['cupsd', 'dbus_daemon'] end end @@ -210,6 +210,22 @@ describe provider_class do output = provider.set_content_flags(content,"-d") output.should match_array(['cupsd_flags="-d"']) end + + it "does not set empty flags for package scripts" do + content = [] + provider = described_class.new(Puppet::Type.type(:service).new(:name => 'cupsd')) + provider.expects(:in_base?).returns(false) + output = provider.set_content_flags(content,'') + output.should match_array([nil]) + end + + it "does set empty flags for base scripts" do + content = [] + provider = described_class.new(Puppet::Type.type(:service).new(:name => 'ntpd')) + provider.expects(:in_base?).returns(true) + output = provider.set_content_flags(content,'') + output.should match_array(['ntpd_flags=""']) + end end describe "#remove_content_flags" do @@ -229,4 +245,12 @@ describe provider_class do provider.set_content_scripts(content,scripts).should match_array(['pkg_scripts="dbus_daemon cupsd"']) end end + + describe "#in_base?" do + it "should true if in base" do + File.stubs(:readlines).with('/etc/rc.conf').returns(['sshd_flags=""']) + provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd')) + provider.in_base?.should be_true + end + end end diff --git a/spec/unit/provider/service/upstart_spec.rb b/spec/unit/provider/service/upstart_spec.rb index 06be70acb..d31511626 100755 --- a/spec/unit/provider/service/upstart_spec.rb +++ b/spec/unit/provider/service/upstart_spec.rb @@ -22,6 +22,11 @@ describe Puppet::Type.type(:service).provider(:upstart) do provider_class.stubs(:which).with("/sbin/initctl").returns("/sbin/initctl") end + it "should be the default provider on Ubuntu" do + Facter.expects(:value).with(:operatingsystem).returns("Ubuntu") + described_class.default?.should be_true + end + describe "excluding services" do it "ignores tty and serial on Redhat systems" do Facter.stubs(:value).with(:osfamily).returns('RedHat') @@ -50,7 +55,13 @@ describe Puppet::Type.type(:service).provider(:upstart) do end it "should not find excluded services" do - processes = "wait-for-state stop/waiting\nportmap-wait start/running\nidmapd-mounting stop/waiting\nstartpar-bridge start/running" + processes = "wait-for-state stop/waiting" + processes += "\nportmap-wait start/running" + processes += "\nidmapd-mounting stop/waiting" + processes += "\nstartpar-bridge start/running" + processes += "\ncryptdisks-udev stop/waiting" + processes += "\nstatd-mounting stop/waiting" + processes += "\ngssd-mounting stop/waiting" provider_class.stubs(:execpipe).yields(processes) provider_class.instances.should be_empty end diff --git a/spec/unit/provider/ssh_authorized_key/parsed_spec.rb b/spec/unit/provider/ssh_authorized_key/parsed_spec.rb index d0cd4e850..2e88c57df 100755 --- a/spec/unit/provider/ssh_authorized_key/parsed_spec.rb +++ b/spec/unit/provider/ssh_authorized_key/parsed_spec.rb @@ -99,6 +99,12 @@ describe provider_class, :unless => Puppet.features.microsoft_windows? do @provider_class.parse_options(optionstr).should == options end + it "should parse quoted options" do + line = 'command="/usr/local/bin/mybin \"$SSH_ORIGINAL_COMMAND\"" ssh-rsa xxx mykey' + + @provider_class.parse(line)[0][:options][0].should == 'command="/usr/local/bin/mybin \"$SSH_ORIGINAL_COMMAND\""' + end + it "should use '' as name for entries that lack a comment" do line = "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAut8aOSxenjOqF527dlsdHWV4MNoAsX14l9M297+SQXaQ5Z3BedIxZaoQthkDALlV/25A1COELrg9J2MqJNQc8Xe9XQOIkBQWWinUlD/BXwoOTWEy8C8zSZPHZ3getMMNhGTBO+q/O+qiJx3y5cA4MTbw2zSxukfWC87qWwcZ64UUlegIM056vPsdZWFclS9hsROVEa57YUMrehQ1EGxT4Z5j6zIopufGFiAPjZigq/vqgcAqhAKP6yu4/gwO6S9tatBeEjZ8fafvj1pmvvIplZeMr96gHE7xS3pEEQqnB3nd4RY7AF6j9kFixnsytAUO7STPh/M3pLiVQBN89TvWPQ==" diff --git a/spec/unit/provider/user/user_role_add_spec.rb b/spec/unit/provider/user/user_role_add_spec.rb index 8dd48e767..42cc4995e 100755 --- a/spec/unit/provider/user/user_role_add_spec.rb +++ b/spec/unit/provider/user/user_role_add_spec.rb @@ -317,7 +317,7 @@ EOT describe "#shadow_entry" do it "should return the line for the right user" do File.stubs(:readlines).returns(["someuser:!:10:5:20:7:1::\n", "fakeval:*:20:10:30:7:2::\n", "testuser:*:30:15:40:7:3::\n"]) - provider.shadow_entry.should == ["fakeval", "*", "20", "10", "30", "7", "2"] + provider.shadow_entry.should == ["fakeval", "*", "20", "10", "30", "7", "2", "", ""] end end @@ -331,5 +331,27 @@ EOT File.stubs(:readlines).returns(["fakeval:NP:12345::::::\n"]) provider.password_max_age.should == -1 end + + it "should return -1 for no maximum when failed attempts are present" do + File.stubs(:readlines).returns(["fakeval:NP:12345::::::3\n"]) + provider.password_max_age.should == -1 + end + end + + describe "#password_min_age" do + it "should return a minimum age number" do + File.stubs(:readlines).returns(["fakeval:NP:12345:10:50::::\n"]) + provider.password_min_age.should == "10" + end + + it "should return -1 for no minimum" do + File.stubs(:readlines).returns(["fakeval:NP:12345::::::\n"]) + provider.password_min_age.should == -1 + end + + it "should return -1 for no minimum when failed attempts are present" do + File.stubs(:readlines).returns(["fakeval:NP:12345::::::3\n"]) + provider.password_min_age.should == -1 + end end end diff --git a/spec/unit/provider/user/windows_adsi_spec.rb b/spec/unit/provider/user/windows_adsi_spec.rb index 8d3ed1d0a..84aa8a74c 100755 --- a/spec/unit/provider/user/windows_adsi_spec.rb +++ b/spec/unit/provider/user/windows_adsi_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -describe Puppet::Type.type(:user).provider(:windows_adsi) do +describe Puppet::Type.type(:user).provider(:windows_adsi), :if => Puppet.features.microsoft_windows? do let(:resource) do Puppet::Type.type(:user).new( :title => 'testuser', @@ -16,8 +16,8 @@ describe Puppet::Type.type(:user).provider(:windows_adsi) do let(:connection) { stub 'connection' } before :each do - Puppet::Util::ADSI.stubs(:computer_name).returns('testcomputername') - Puppet::Util::ADSI.stubs(:connect).returns connection + Puppet::Util::Windows::ADSI.stubs(:computer_name).returns('testcomputername') + Puppet::Util::Windows::ADSI.stubs(:connect).returns connection end describe ".instances" do @@ -30,8 +30,8 @@ describe Puppet::Type.type(:user).provider(:windows_adsi) do end end - it "should provide access to a Puppet::Util::ADSI::User object" do - provider.user.should be_a(Puppet::Util::ADSI::User) + it "should provide access to a Puppet::Util::Windows::ADSI::User object" do + provider.user.should be_a(Puppet::Util::Windows::ADSI::User) end describe "when managing groups" do @@ -68,7 +68,7 @@ describe Puppet::Type.type(:user).provider(:windows_adsi) do resource[:home] = 'C:\Users\testuser' user = stub 'user' - Puppet::Util::ADSI::User.expects(:create).with('testuser').returns user + Puppet::Util::Windows::ADSI::User.expects(:create).with('testuser').returns user user.stubs(:groups).returns(['group2', 'group3']) @@ -82,12 +82,12 @@ describe Puppet::Type.type(:user).provider(:windows_adsi) do provider.create end - it "should load the profile if managehome is set", :if => Puppet.features.microsoft_windows? do + it "should load the profile if managehome is set" do resource[:password] = '0xDeadBeef' resource[:managehome] = true user = stub_everything 'user' - Puppet::Util::ADSI::User.expects(:create).with('testuser').returns user + Puppet::Util::Windows::ADSI::User.expects(:create).with('testuser').returns user Puppet::Util::Windows::User.expects(:load_profile).with('testuser', '0xDeadBeef') provider.create @@ -115,18 +115,18 @@ describe Puppet::Type.type(:user).provider(:windows_adsi) do end it 'should not create a user if a group by the same name exists' do - Puppet::Util::ADSI::User.expects(:create).with('testuser').raises( Puppet::Error.new("Cannot create user if group 'testuser' exists.") ) + Puppet::Util::Windows::ADSI::User.expects(:create).with('testuser').raises( Puppet::Error.new("Cannot create user if group 'testuser' exists.") ) expect{ provider.create }.to raise_error( Puppet::Error, /Cannot create user if group 'testuser' exists./ ) end end it 'should be able to test whether a user exists' do - Puppet::Util::ADSI.stubs(:sid_uri_safe).returns(nil) - Puppet::Util::ADSI.stubs(:connect).returns stub('connection') + Puppet::Util::Windows::ADSI.stubs(:sid_uri_safe).returns(nil) + Puppet::Util::Windows::ADSI.stubs(:connect).returns stub('connection') provider.should be_exists - Puppet::Util::ADSI.stubs(:connect).returns nil + Puppet::Util::Windows::ADSI.stubs(:connect).returns nil provider.should_not be_exists end @@ -136,12 +136,12 @@ describe Puppet::Type.type(:user).provider(:windows_adsi) do provider.delete end - it 'should delete the profile if managehome is set', :if => Puppet.features.microsoft_windows? do + it 'should delete the profile if managehome is set' do resource[:managehome] = true sid = 'S-A-B-C' - Puppet::Util::Windows::Security.expects(:name_to_sid).with('testuser').returns(sid) - Puppet::Util::ADSI::UserProfile.expects(:delete).with(sid) + Puppet::Util::Windows::SID.expects(:name_to_sid).with('testuser').returns(sid) + Puppet::Util::Windows::ADSI::UserProfile.expects(:delete).with(sid) connection.expects(:Delete).with('user', 'testuser') provider.delete @@ -153,8 +153,8 @@ describe Puppet::Type.type(:user).provider(:windows_adsi) do provider.flush end - it "should return the user's SID as uid", :if => Puppet.features.microsoft_windows? do - Puppet::Util::Windows::Security.expects(:name_to_sid).with('testuser').returns('S-1-5-21-1362942247-2130103807-3279964888-1111') + it "should return the user's SID as uid" do + Puppet::Util::Windows::SID.expects(:name_to_sid).with('testuser').returns('S-1-5-21-1362942247-2130103807-3279964888-1111') provider.uid.should == 'S-1-5-21-1362942247-2130103807-3279964888-1111' end diff --git a/spec/unit/reports/store_spec.rb b/spec/unit/reports/store_spec.rb index 7866571a1..7f94f7d1b 100755 --- a/spec/unit/reports/store_spec.rb +++ b/spec/unit/reports/store_spec.rb @@ -30,25 +30,9 @@ describe processor do File.read(File.join(Puppet[:reportdir], @report.host, "201101061200.yaml")).should == @report.to_yaml end - it "should write to the report directory in the correct sequence" do - # By doing things in this sequence we should protect against race - # conditions - Time.stubs(:now).returns(Time.parse("2011-01-06 12:00:00 UTC")) - writeseq = sequence("write") - file = mock "file" - Tempfile.expects(:new).in_sequence(writeseq).returns(file) - file.expects(:chmod).in_sequence(writeseq).with(0640) - file.expects(:print).with(@report.to_yaml).in_sequence(writeseq) - file.expects(:close).in_sequence(writeseq) - file.stubs(:path).returns(File.join(Dir.tmpdir, "foo123")) - FileUtils.expects(:mv).in_sequence(writeseq).with(File.join(Dir.tmpdir, "foo123"), File.join(Puppet[:reportdir], @report.host, "201101061200.yaml")) - @report.process - end - it "rejects invalid hostnames" do @report.host = ".." Puppet::FileSystem.expects(:exist?).never - Tempfile.expects(:new).never expect { @report.process }.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 741f60ef3..80933dc41 100755 --- a/spec/unit/resource/catalog_spec.rb +++ b/spec/unit/resource/catalog_spec.rb @@ -606,7 +606,6 @@ describe Puppet::Resource::Catalog, "when compiling" do @catalog.apply end - after { Puppet.settings.clear } end describe "non-host catalogs" do @@ -627,7 +626,6 @@ describe Puppet::Resource::Catalog, "when compiling" do @catalog.apply end - after { Puppet.settings.clear } end end @@ -659,9 +657,6 @@ describe Puppet::Resource::Catalog, "when compiling" do @catalog.write_graph(@name) end - after do - Puppet.settings.clear - end end describe "when indirecting" do diff --git a/spec/unit/resource_spec.rb b/spec/unit/resource_spec.rb index 73c27c1fe..e954a2179 100755 --- a/spec/unit/resource_spec.rb +++ b/spec/unit/resource_spec.rb @@ -592,7 +592,7 @@ describe Puppet::Resource do text = @resource.render('yaml') newresource = Puppet::Resource.convert_from('yaml', text) - newresource.should equal_attributes_of @resource + newresource.should equal_resource_attributes_of @resource end end @@ -615,7 +615,7 @@ describe Puppet::Resource do text = @resource.render('yaml') newresource = Puppet::Resource.convert_from('yaml', text) - newresource.should equal_attributes_of @resource + newresource.should equal_resource_attributes_of @resource end end diff --git a/spec/unit/settings/array_setting_spec.rb b/spec/unit/settings/array_setting_spec.rb new file mode 100644 index 000000000..05cc28d6a --- /dev/null +++ b/spec/unit/settings/array_setting_spec.rb @@ -0,0 +1,39 @@ +require 'spec_helper' + +require 'puppet/settings' +require 'puppet/settings/array_setting' + +describe Puppet::Settings::ArraySetting do + subject { described_class.new(:settings => stub('settings'), :desc => "test") } + + it "is of type :array" do + expect(subject.type).to eq :array + end + + describe "munging the value" do + describe "when given a string" do + it "splits multiple values into an array" do + expect(subject.munge("foo,bar")).to eq %w[foo bar] + end + it "strips whitespace between elements" do + expect(subject.munge("foo , bar")).to eq %w[foo bar] + end + + it "creates an array when one item is given" do + expect(subject.munge("foo")).to eq %w[foo] + end + end + + describe "when given an array" do + it "returns the array" do + expect(subject.munge(%w[foo])).to eq %w[foo] + end + end + + it "raises an error when given an unexpected object type" do + expect { + subject.munge({:foo => 'bar'}) + }.to raise_error(ArgumentError, "Expected an Array or String, got a Hash") + end + end +end diff --git a/spec/unit/settings/autosign_setting_spec.rb b/spec/unit/settings/autosign_setting_spec.rb index 0c8184c8a..0dbfe4ecb 100644 --- a/spec/unit/settings/autosign_setting_spec.rb +++ b/spec/unit/settings/autosign_setting_spec.rb @@ -73,7 +73,7 @@ describe Puppet::Settings::AutosignSetting do describe "converting the setting to a resource" do it "converts the file path to a file resource" do path = File.expand_path('/path/to/autosign.conf') - settings.stubs(:value).with('autosign').returns(path) + settings.stubs(:value).with('autosign', nil, false).returns(path) Puppet::FileSystem.stubs(:exist?).with(path).returns true Puppet.stubs(:features).returns(stub(:root? => true, :microsoft_windows? => false)) @@ -91,7 +91,7 @@ describe Puppet::Settings::AutosignSetting do end it "returns nil when the setting is a boolean" do - settings.stubs(:value).with('autosign').returns 'true' + settings.stubs(:value).with('autosign', nil, false).returns 'true' setting.mode = '0664' setting.owner = 'service' diff --git a/spec/unit/settings/environment_conf_spec.rb b/spec/unit/settings/environment_conf_spec.rb index 6a8a6689e..e4a492ae8 100644 --- a/spec/unit/settings/environment_conf_spec.rb +++ b/spec/unit/settings/environment_conf_spec.rb @@ -3,31 +3,47 @@ require 'puppet/settings/environment_conf.rb' describe Puppet::Settings::EnvironmentConf do + def setup_environment_conf(config, conf_hash) + conf_hash.each do |setting,value| + config.expects(:setting).with(setting).returns( + mock('setting', :value => value) + ) + end + end + context "with config" do - let(:config) { stub(:config) } + let(:config) { stub('config') } let(:envconf) { Puppet::Settings::EnvironmentConf.new("/some/direnv", config, ["/global/modulepath"]) } it "reads a modulepath from config and does not include global_module_path" do - config.expects(:setting).with(:modulepath).returns( - mock('setting', :value => '/some/modulepath') - ) + setup_environment_conf(config, :modulepath => '/some/modulepath') + expect(envconf.modulepath).to eq(File.expand_path('/some/modulepath')) end it "reads a manifest from config" do - config.expects(:setting).with(:manifest).returns( - mock('setting', :value => '/some/manifest') - ) + setup_environment_conf(config, :manifest => '/some/manifest') + expect(envconf.manifest).to eq(File.expand_path('/some/manifest')) end it "reads a config_version from config" do - config.expects(:setting).with(:config_version).returns( - mock('setting', :value => '/some/version.sh') - ) + setup_environment_conf(config, :config_version => '/some/version.sh') + expect(envconf.config_version).to eq(File.expand_path('/some/version.sh')) end + it "read an environment_timeout from config" do + setup_environment_conf(config, :environment_timeout => '3m') + + expect(envconf.environment_timeout).to eq(180) + end + + it "can retrieve raw settings" do + setup_environment_conf(config, :manifest => 'manifest.pp') + + expect(envconf.raw_setting(:manifest)).to eq('manifest.pp') + end end context "without config" do @@ -47,5 +63,56 @@ describe Puppet::Settings::EnvironmentConf do it "returns nothing for config_version when config has none" do expect(envconf.config_version).to be_nil end + + it "returns a defult of 0 for environment_timeout when config has none" do + expect(envconf.environment_timeout).to eq(0) + end + + it "can still retrieve raw setting" do + expect(envconf.raw_setting(:manifest)).to be_nil + end + end + + describe "with disable_per_environment_manifest" do + + let(:config) { stub('config') } + let(:envconf) { Puppet::Settings::EnvironmentConf.new("/some/direnv", config, ["/global/modulepath"]) } + + context "set true" do + + before(:each) do + Puppet[:default_manifest] = File.expand_path('/default/manifest') + Puppet[:disable_per_environment_manifest] = true + end + + it "ignores environment.conf manifest" do + setup_environment_conf(config, :manifest => '/some/manifest.pp') + + expect(envconf.manifest).to eq(File.expand_path('/default/manifest')) + end + + it "logs error when environment.conf has manifest set" do + setup_environment_conf(config, :manifest => '/some/manifest.pp') + + envconf.manifest + expect(@logs.first.to_s).to match(/disable_per_environment_manifest.*true.*environment.conf.*does not match the default_manifest/) + end + + it "does not log an error when environment.conf does not have a manifest set" do + setup_environment_conf(config, :manifest => nil) + + expect(envconf.manifest).to eq(File.expand_path('/default/manifest')) + expect(@logs).to be_empty + end + end + + it "uses environment.conf when false" do + setup_environment_conf(config, :manifest => '/some/manifest.pp') + + Puppet[:default_manifest] = File.expand_path('/default/manifest') + Puppet[:disable_per_environment_manifest] = false + + expect(envconf.manifest).to eq(File.expand_path('/some/manifest.pp')) + end end end diff --git a/spec/unit/settings/file_setting_spec.rb b/spec/unit/settings/file_setting_spec.rb index b31d0ccb3..c77cc5981 100755 --- a/spec/unit/settings/file_setting_spec.rb +++ b/spec/unit/settings/file_setting_spec.rb @@ -129,7 +129,7 @@ describe Puppet::Settings::FileSetting do @settings = mock 'settings' @file = Puppet::Settings::FileSetting.new(:settings => @settings, :desc => "eh", :name => :myfile, :section => "mysect") @file.stubs(:create_files?).returns true - @settings.stubs(:value).with(:myfile).returns @basepath + @settings.stubs(:value).with(:myfile, nil, false).returns @basepath end it "should return :file as its type" do @@ -146,19 +146,20 @@ describe Puppet::Settings::FileSetting do it "should manage existent files even if 'create_files' is not enabled" do @file.expects(:create_files?).returns false @file.expects(:type).returns :file + Puppet::FileSystem.stubs(:exist?) Puppet::FileSystem.expects(:exist?).with(@basepath).returns true @file.to_resource.should be_instance_of(Puppet::Resource) end describe "on POSIX systems", :if => Puppet.features.posix? do it "should skip files in /dev" do - @settings.stubs(:value).with(:myfile).returns "/dev/file" + @settings.stubs(:value).with(:myfile, nil, false).returns "/dev/file" @file.to_resource.should be_nil end end it "should skip files whose paths are not strings" do - @settings.stubs(:value).with(:myfile).returns :foo + @settings.stubs(:value).with(:myfile, nil, false).returns :foo @file.to_resource.should be_nil end @@ -169,7 +170,7 @@ describe Puppet::Settings::FileSetting do end it "should fully qualified returned files if necessary (#795)" do - @settings.stubs(:value).with(:myfile).returns "myfile" + @settings.stubs(:value).with(:myfile, nil, false).returns "myfile" path = File.expand_path('myfile') @file.to_resource.title.should == path end diff --git a/spec/unit/settings/priority_setting_spec.rb b/spec/unit/settings/priority_setting_spec.rb index d51e39dc4..62cad5def 100755 --- a/spec/unit/settings/priority_setting_spec.rb +++ b/spec/unit/settings/priority_setting_spec.rb @@ -53,10 +53,10 @@ describe Puppet::Settings::PrioritySetting do 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 + 'high' => Puppet::Util::Windows::Process::HIGH_PRIORITY_CLASS, + 'normal' => Puppet::Util::Windows::Process::NORMAL_PRIORITY_CLASS, + 'low' => Puppet::Util::Windows::Process::BELOW_NORMAL_PRIORITY_CLASS, + 'idle' => Puppet::Util::Windows::Process::IDLE_PRIORITY_CLASS }.each do |value, converted_value| setting.munge(value).should == converted_value end diff --git a/spec/unit/settings_spec.rb b/spec/unit/settings_spec.rb index 66ec67607..0a1cee2bd 100755 --- a/spec/unit/settings_spec.rb +++ b/spec/unit/settings_spec.rb @@ -674,7 +674,7 @@ describe Puppet::Settings do @settings.send(:parse_config_files) expect(@settings.value(:manifestdir)).to eq("/somewhere/production/manifests") end - + it "interpolates the set environment when no environment specified" do text = <<-EOF [main] @@ -1003,102 +1003,95 @@ describe Puppet::Settings do end describe "deprecations" do - context "in puppet.conf" do - - def assert_puppet_conf_deprecation(setting, matches) - Puppet.expects(:deprecation_warning).with(regexp_matches(matches), anything) - - val = "/you/can/set/this/but/will/get/warning" - text = "[main] - #{setting}=#{val} - " - Puppet.settings.parse_config(text) - end - - it "warns when manifest is set" do - assert_puppet_conf_deprecation('manifest', /manifest.*puppet.conf/) - end - - it "warns when modulepath is set" do - assert_puppet_conf_deprecation('modulepath', /modulepath.*puppet.conf/) - end - - it "warns when config_version is set" do - assert_puppet_conf_deprecation('config_version', /config_version.*puppet.conf/) - end - - it "warns when manifestdir is set" do - assert_puppet_conf_deprecation('manifestdir', /Setting manifestdir.*is.*deprecated/) - end - - it "warns when templatedir is set" do - assert_puppet_conf_deprecation('templatedir', /Setting templatedir.*is.*deprecated/) - end + let(:settings) { Puppet::Settings.new } + let(:app_defaults) { + { + :logdir => "/dev/null", + :confdir => "/dev/null", + :vardir => "/dev/null", + } + } + + def assert_accessing_setting_is_deprecated(settings, setting) + Puppet.expects(:deprecation_warning).with("Accessing '#{setting}' as a setting is deprecated. See http://links.puppetlabs.com/env-settings-deprecations") + Puppet.expects(:deprecation_warning).with("Modifying '#{setting}' as a setting is deprecated. See http://links.puppetlabs.com/env-settings-deprecations") + settings[setting.intern] = apath = File.expand_path('foo') + expect(settings[setting.intern]).to eq(apath) end - context "on the command line" do - def assert_command_line_deprecation(setting, message) - Puppet.expects(:deprecation_warning).with(message, anything) - - args = ["--#{setting}", "/some/value"] - Puppet.settings.send(:parse_global_options, args) - end - - def assert_command_line_not_deprecated(setting) - Puppet.expects(:deprecation_warning).never - - args = ["--#{setting}", "/some/value"] - Puppet.settings.send(:parse_global_options, args) + before(:each) do + settings.define_settings(:main, { + :logdir => { :default => 'a', :desc => 'a' }, + :confdir => { :default => 'b', :desc => 'b' }, + :vardir => { :default => 'c', :desc => 'c' }, + }) + end + + context "complete" do + let(:completely_deprecated_settings) do + settings.define_settings(:main, { + :manifestdir => { + :default => 'foo', + :desc => 'a deprecated setting', + :deprecated => :completely, + } + }) + settings end - it "does not warn when manifest is set on command line" do - assert_command_line_not_deprecated('manifest') - end + it "warns when set in puppet.conf" do + Puppet.expects(:deprecation_warning).with(regexp_matches(/manifestdir is deprecated\./), 'setting-manifestdir') - it "does not warn when modulepath is set on command line" do - assert_command_line_not_deprecated('modulepath') + completely_deprecated_settings.parse_config(<<-CONF) + manifestdir='should warn' + CONF + completely_deprecated_settings.initialize_app_defaults(app_defaults) end - it "does not warn when config_version is set on command line" do - assert_command_line_not_deprecated('config_version') - end + it "warns when set on the commandline" do + Puppet.expects(:deprecation_warning).with(regexp_matches(/manifestdir is deprecated\./), 'setting-manifestdir') - it "warns when manifestdir is set on command line" do - assert_command_line_deprecation('manifestdir', "Setting manifestdir is deprecated. See http://links.puppetlabs.com/env-settings-deprecations") + args = ["--manifestdir", "/some/value"] + completely_deprecated_settings.send(:parse_global_options, args) + completely_deprecated_settings.initialize_app_defaults(app_defaults) end - it "warns when templatedir is set on command line" do - assert_command_line_deprecation('templatedir', "Setting templatedir is deprecated. See http://links.puppetlabs.com/env-settings-deprecations") + it "warns when set in code" do + assert_accessing_setting_is_deprecated(completely_deprecated_settings, 'manifestdir') end end - context "as settings in the code base" do - def assert_accessing_setting_is_deprecated(setting) - Puppet.expects(:deprecation_warning).with("Accessing '#{setting}' as a setting is deprecated. See http://links.puppetlabs.com/env-settings-deprecations") - Puppet.expects(:deprecation_warning).with("Modifying '#{setting}' as a setting is deprecated. See http://links.puppetlabs.com/env-settings-deprecations") - Puppet[setting.intern] = apath = File.expand_path('foo') - expect(Puppet[setting.intern]).to eq(apath) + context "partial" do + let(:partially_deprecated_settings) do + settings.define_settings(:main, { + :modulepath => { + :default => 'foo', + :desc => 'a partially deprecated setting', + :deprecated => :allowed_on_commandline, + } + }) + settings end - it "warns when attempt to access a 'manifest' setting" do - assert_accessing_setting_is_deprecated('manifest') + it "warns for a deprecated setting allowed on the command line set in puppet.conf" do + Puppet.expects(:deprecation_warning).with(regexp_matches(/modulepath is deprecated in puppet\.conf/), 'puppet-conf-setting-modulepath') + partially_deprecated_settings.parse_config(<<-CONF) + modulepath='should warn' + CONF + partially_deprecated_settings.initialize_app_defaults(app_defaults) end - it "warns when attempt to access a 'modulepath' setting" do - assert_accessing_setting_is_deprecated('modulepath') - end - it "warns when attempt to access a 'config_version' setting" do - assert_accessing_setting_is_deprecated('config_version') - end + it "does not warn when manifest is set on command line" do + Puppet.expects(:deprecation_warning).never - it "warns when attempt to access a 'manifestdir' setting" do - assert_accessing_setting_is_deprecated('manifestdir') + args = ["--modulepath", "/some/value"] + partially_deprecated_settings.send(:parse_global_options, args) + partially_deprecated_settings.initialize_app_defaults(app_defaults) end - it "warns when attempt to access a 'templatedir' setting" do - assert_accessing_setting_is_deprecated('templatedir') + it "warns when set in code" do + assert_accessing_setting_is_deprecated(partially_deprecated_settings, 'modulepath') end - end end end @@ -1366,6 +1359,44 @@ describe Puppet::Settings do end end + describe "adding default directory environment to the catalog" do + let(:tmpenv) { tmpdir("envs") } + let(:default_path) { "#{tmpenv}/environments" } + before(:each) do + @settings.define_settings :main, + :environment => { :default => "production", :desc => "env"}, + :environmentpath => { :type => :path, :default => default_path, :desc => "envpath"} + end + + it "adds if environmentpath exists" do + envpath = "#{tmpenv}/custom_envpath" + @settings[:environmentpath] = envpath + Dir.mkdir(envpath) + catalog = @settings.to_catalog + expect(catalog.resource_keys).to include(["File", "#{envpath}/production"]) + end + + it "adds the first directory of environmentpath" do + envdir = "#{tmpenv}/custom_envpath" + envpath = "#{envdir}#{File::PATH_SEPARATOR}/some/other/envdir" + @settings[:environmentpath] = envpath + Dir.mkdir(envdir) + catalog = @settings.to_catalog + expect(catalog.resource_keys).to include(["File", "#{envdir}/production"]) + end + + it "handles a non-existent environmentpath" do + catalog = @settings.to_catalog + expect(catalog.resource_keys).to be_empty + end + + it "handles a default environmentpath" do + Dir.mkdir(default_path) + catalog = @settings.to_catalog + expect(catalog.resource_keys).to include(["File", "#{default_path}/production"]) + end + end + describe "when adding users and groups to the catalog" do before do Puppet.features.stubs(:root?).returns true @@ -1475,14 +1506,14 @@ describe Puppet::Settings do :maindir => { :type => :directory, :default => make_absolute("/maindir"), :desc => "a" }, :seconddir => { :type => :directory, :default => make_absolute("/seconddir"), :desc => "a"} @settings.define_settings :main, :user => { :default => "suser", :desc => "doc" }, :group => { :default => "sgroup", :desc => "doc" } - @settings.define_settings :other, :otherdir => {:type => :directory, :default => make_absolute("/otherdir"), :desc => "a", :owner => "service", :group => "service", :mode => 0755} + @settings.define_settings :other, :otherdir => {:type => :directory, :default => make_absolute("/otherdir"), :desc => "a", :owner => "service", :group => "service", :mode => '0755'} @settings.define_settings :third, :thirddir => { :type => :directory, :default => make_absolute("/thirddir"), :desc => "b"} - @settings.define_settings :files, :myfile => {:type => :file, :default => make_absolute("/myfile"), :desc => "a", :mode => 0755} + @settings.define_settings :files, :myfile => {:type => :file, :default => make_absolute("/myfile"), :desc => "a", :mode => '0755'} 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) + Dir.expects(:mkdir).with(make_absolute("/otherdir"), '0755') @settings.mkdir(:otherdir) end diff --git a/spec/unit/ssl/certificate_authority_spec.rb b/spec/unit/ssl/certificate_authority_spec.rb index ef5a86862..2881b0a1e 100755 --- a/spec/unit/ssl/certificate_authority_spec.rb +++ b/spec/unit/ssl/certificate_authority_spec.rb @@ -7,7 +7,6 @@ require 'puppet/ssl/certificate_authority' describe Puppet::SSL::CertificateAuthority do after do Puppet::SSL::CertificateAuthority.instance_variable_set(:@singleton_instance, nil) - Puppet.settings.clearused end def stub_ca_host @@ -937,12 +936,36 @@ describe Puppet::SSL::CertificateAuthority do cert = stub 'cert', :content => real_cert Puppet::SSL::Certificate.indirection.expects(:find).with("host").returns nil - @ca.inventory.expects(:serial).with("host").returns 16 + @ca.inventory.expects(:serials).with("host").returns [16] @ca.crl.expects(:revoke).with { |serial, key| serial == 16 } @ca.revoke('host') end + it "should revoke all serials matching a name" do + real_cert = stub 'real_cert', :serial => 15 + cert = stub 'cert', :content => real_cert + Puppet::SSL::Certificate.indirection.expects(:find).with("host").returns nil + + @ca.inventory.expects(:serials).with("host").returns [16, 20, 25] + + @ca.crl.expects(:revoke).with { |serial, key| serial == 16 } + @ca.crl.expects(:revoke).with { |serial, key| serial == 20 } + @ca.crl.expects(:revoke).with { |serial, key| serial == 25 } + @ca.revoke('host') + end + + it "should raise an error if no certificate match" do + real_cert = stub 'real_cert', :serial => 15 + cert = stub 'cert', :content => real_cert + Puppet::SSL::Certificate.indirection.expects(:find).with("host").returns nil + + @ca.inventory.expects(:serials).with("host").returns [] + + @ca.crl.expects(:revoke).never + expect { @ca.revoke('host') }.to raise_error + end + context "revocation by serial number (#16798)" do it "revokes when given a lower case hexadecimal formatted string" do @ca.crl.expects(:revoke).with { |serial, key| serial == 15 } diff --git a/spec/unit/ssl/inventory_spec.rb b/spec/unit/ssl/inventory_spec.rb index 6e4fbd340..879fd90d1 100755 --- a/spec/unit/ssl/inventory_spec.rb +++ b/spec/unit/ssl/inventory_spec.rb @@ -133,5 +133,18 @@ describe Puppet::SSL::Inventory, :unless => Puppet.features.microsoft_windows? d @inventory.serial("me").should == 15 end end + + describe "and finding all serial numbers" do + it "should return nil if the inventory file is missing" do + Puppet::FileSystem.expects(:exist?).with(cert_inventory).returns false + @inventory.serials(:whatever).should be_empty + end + + it "should return the all the serial numbers from the lines matching the provided name" do + File.expects(:readlines).with(cert_inventory).returns ["0x00f blah blah /CN=me\n", "0x001 blah blah /CN=you\n", "0x002 blah blah /CN=me\n"] + + @inventory.serials("me").should == [15, 2] + end + end end end diff --git a/spec/unit/ssl/validator_spec.rb b/spec/unit/ssl/validator_spec.rb index 2b8cfb0f9..ade1575dc 100644 --- a/spec/unit/ssl/validator_spec.rb +++ b/spec/unit/ssl/validator_spec.rb @@ -1,6 +1,5 @@ require 'spec_helper' require 'puppet/ssl' -require 'puppet/ssl/configuration' describe Puppet::SSL::Validator::DefaultValidator do let(:ssl_context) do diff --git a/spec/unit/transaction/resource_harness_spec.rb b/spec/unit/transaction/resource_harness_spec.rb index 5eeaf0ba4..7d9c6f439 100755 --- a/spec/unit/transaction/resource_harness_spec.rb +++ b/spec/unit/transaction/resource_harness_spec.rb @@ -352,6 +352,70 @@ describe Puppet::Transaction::ResourceHarness do event.status.should != 'failure' end end + + it "should not ignore microseconds when auditing a file's mtime" do + test_file = tmpfile('foo') + File.open(test_file, 'w').close + resource = Puppet::Type.type(:file).new :path => test_file, :audit => ['mtime'], :backup => false + + # construct a property hash with nanosecond resolution as would be + # found on an ext4 file system + time_with_nsec_resolution = Time.at(1000, 123456.999) + current_from_filesystem = {:mtime => time_with_nsec_resolution} + + # construct a property hash with a 1 microsecond difference from above + time_with_usec_resolution = Time.at(1000, 123457.000) + historical_from_state_yaml = {:mtime => time_with_usec_resolution} + + # set up the sequence of stubs; yeah, this is pretty + # brittle, so this might need to be adjusted if the + # resource_harness logic changes + resource.expects(:retrieve).returns(current_from_filesystem) + Puppet::Util::Storage.stubs(:cache).with(resource). + returns(historical_from_state_yaml).then. + returns(current_from_filesystem).then. + returns(current_from_filesystem) + + # there should be an audit change recorded, since the two + # timestamps differ by at least 1 microsecond + status = @harness.evaluate(resource) + status.events.should_not be_empty + status.events.each do |event| + event.message.should =~ /audit change: previously recorded/ + end + end + + it "should ignore nanoseconds when auditing a file's mtime" do + test_file = tmpfile('foo') + File.open(test_file, 'w').close + resource = Puppet::Type.type(:file).new :path => test_file, :audit => ['mtime'], :backup => false + + # construct a property hash with nanosecond resolution as would be + # found on an ext4 file system + time_with_nsec_resolution = Time.at(1000, 123456.789) + current_from_filesystem = {:mtime => time_with_nsec_resolution} + + # construct a property hash with the same timestamp as above, + # truncated to microseconds, as would be read back from state.yaml + time_with_usec_resolution = Time.at(1000, 123456.000) + historical_from_state_yaml = {:mtime => time_with_usec_resolution} + + # set up the sequence of stubs; yeah, this is pretty + # brittle, so this might need to be adjusted if the + # resource_harness logic changes + resource.expects(:retrieve).returns(current_from_filesystem) + Puppet::Util::Storage.stubs(:cache).with(resource). + returns(historical_from_state_yaml).then. + returns(current_from_filesystem).then. + returns(current_from_filesystem) + + # there should be no audit change recorded, despite the + # slight difference in the two timestamps + status = @harness.evaluate(resource) + status.events.each do |event| + event.message.should_not =~ /audit change: previously recorded/ + end + end end describe "when applying changes" do diff --git a/spec/unit/transaction_spec.rb b/spec/unit/transaction_spec.rb index 04ce08c4f..bf7820227 100755 --- a/spec/unit/transaction_spec.rb +++ b/spec/unit/transaction_spec.rb @@ -37,7 +37,8 @@ describe Puppet::Transaction do # This will basically only ever be used during testing. it "should automatically create resource statuses if asked for a non-existent status" do resource = Puppet::Type.type(:notify).new :title => "foobar" - @transaction.resource_status(resource).should be_instance_of(Puppet::Resource::Status) + transaction = transaction_with_resource(resource) + transaction.resource_status(resource).should be_instance_of(Puppet::Resource::Status) end it "should add provided resource statuses to its report" do @@ -72,15 +73,15 @@ describe Puppet::Transaction do describe "when initializing" do it "should create an event manager" do - @transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new, nil, nil) - @transaction.event_manager.should be_instance_of(Puppet::Transaction::EventManager) - @transaction.event_manager.transaction.should equal(@transaction) + transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new, nil, nil) + transaction.event_manager.should be_instance_of(Puppet::Transaction::EventManager) + transaction.event_manager.transaction.should equal(transaction) end it "should create a resource harness" do - @transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new, nil, nil) - @transaction.resource_harness.should be_instance_of(Puppet::Transaction::ResourceHarness) - @transaction.resource_harness.transaction.should equal(@transaction) + transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new, nil, nil) + transaction.resource_harness.should be_instance_of(Puppet::Transaction::ResourceHarness) + transaction.resource_harness.transaction.should equal(transaction) end it "should set retrieval time on the report" do @@ -95,29 +96,25 @@ describe Puppet::Transaction do end describe "when evaluating a resource" do - before do - @catalog = Puppet::Resource::Catalog.new - @resource = Puppet::Type.type(:file).new :path => @basepath - @catalog.add_resource(@resource) - - @transaction = Puppet::Transaction.new(@catalog, nil, Puppet::Graph::RandomPrioritizer.new) - @transaction.stubs(:skip?).returns false - end + let(:resource) { Puppet::Type.type(:file).new :path => @basepath } it "should process events" do - @transaction.event_manager.expects(:process_events).with(@resource) + transaction = transaction_with_resource(resource) - @transaction.evaluate + transaction.expects(:skip?).with(resource).returns false + transaction.event_manager.expects(:process_events).with(resource) + + transaction.evaluate end describe "and the resource should be skipped" do - before do - @transaction.expects(:skip?).with(@resource).returns true - end - it "should mark the resource's status as skipped" do - @transaction.evaluate - @transaction.resource_status(@resource).should be_skipped + transaction = transaction_with_resource(resource) + + transaction.expects(:skip?).with(resource).returns true + + transaction.evaluate + transaction.resource_status(resource).should be_skipped end end end @@ -288,6 +285,9 @@ describe Puppet::Transaction do before :each do catalog.add_resource generator generator.stubs(:generate).returns generated + # avoid crude failures because of nil resources that result + # from implicit containment and lacking containers + catalog.stubs(:container_of).returns generator end it "should call 'generate' on all created resources" do @@ -313,6 +313,31 @@ describe Puppet::Transaction do end end + describe "when performing pre-run checks" do + let(:resource) { Puppet::Type.type(:notify).new(:title => "spec") } + let(:transaction) { transaction_with_resource(resource) } + let(:spec_exception) { 'spec-exception' } + + it "should invoke each resource's hook and apply the catalog after no failures" do + resource.expects(:pre_run_check) + + transaction.evaluate + end + + it "should abort the transaction on failure" do + resource.expects(:pre_run_check).raises(Puppet::Error, spec_exception) + + expect { transaction.evaluate }.to raise_error(Puppet::Error, /Some pre-run checks failed/) + end + + it "should log the resource-specific exception" do + resource.expects(:pre_run_check).raises(Puppet::Error, spec_exception) + resource.expects(:log_exception).with(responds_with(:message, spec_exception)) + + expect { transaction.evaluate }.to raise_error(Puppet::Error) + end + end + describe "when skipping a resource" do before :each do @resource = Puppet::Type.type(:notify).new :name => "foo" @@ -478,44 +503,70 @@ describe Puppet::Transaction do end describe "during teardown" do + let(:catalog) { Puppet::Resource::Catalog.new } + let(:transaction) do + Puppet::Transaction.new(catalog, nil, Puppet::Graph::RandomPrioritizer.new) + end + + let(:teardown_type) do + Puppet::Type.newtype(:teardown_test) do + newparam(:name) {} + end + end + before :each do - @catalog = Puppet::Resource::Catalog.new - @transaction = Puppet::Transaction.new(@catalog, nil, Puppet::Graph::RandomPrioritizer.new) + teardown_type.provide(:teardown_provider) do + class << self + attr_reader :result + + def post_resource_eval + @result = 'passed' + end + end + end end it "should call ::post_resource_eval on provider classes that support it" do - @resource = Puppet::Type.type(:notify).new :title => "foo" - @catalog.add_resource @resource + resource = teardown_type.new(:title => "foo", :provider => :teardown_provider) - # 'expects' will cause 'respond_to?(:post_resource_eval)' to return true - @resource.provider.class.expects(:post_resource_eval) - @transaction.evaluate + transaction = transaction_with_resource(resource) + transaction.evaluate + + expect(resource.provider.class.result).to eq('passed') end it "should call ::post_resource_eval even if other providers' ::post_resource_eval fails" do - @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) + teardown_type.provide(:always_fails) do + class << self + attr_reader :result + + def post_resource_eval + @result = 'failed' + raise Puppet::Error, "This provider always fails" + end + end + end - @transaction.evaluate + good_resource = teardown_type.new(:title => "bloo", :provider => :teardown_provider) + bad_resource = teardown_type.new(:title => "blob", :provider => :always_fails) + + catalog.add_resource(bad_resource) + catalog.add_resource(good_resource) + + transaction.evaluate + + expect(good_resource.provider.class.result).to eq('passed') + expect(bad_resource.provider.class.result).to eq('failed') end it "should call ::post_resource_eval even if one of the resources fails" do - @resource3 = Puppet::Type.type(:notify).new :title => "bloo" - @resource3.stubs(:retrieve_resource).raises - @catalog.add_resource @resource3 + resource = teardown_type.new(:title => "foo", :provider => :teardown_provider) + resource.stubs(:retrieve_resource).raises + catalog.add_resource resource - @resource3.provider.class.expects(:post_resource_eval) + resource.provider.class.expects(:post_resource_eval) - @transaction.evaluate + transaction.evaluate end end diff --git a/spec/unit/type/cron_spec.rb b/spec/unit/type/cron_spec.rb index b4a853173..82f646290 100755 --- a/spec/unit/type/cron_spec.rb +++ b/spec/unit/type/cron_spec.rb @@ -452,7 +452,7 @@ describe Puppet::Type.type(:cron), :unless => Puppet.features.microsoft_windows? describe "special" do %w(reboot yearly annually monthly weekly daily midnight hourly).each do |value| it "should support the value '#{value}'" do - expect { described_class.new(:name => 'foo', :special => value ) }.to_not raise_error(Puppet::Error, /cannot specify both a special schedule and a value/) + expect { described_class.new(:name => 'foo', :special => value ) }.to_not raise_error end end @@ -462,7 +462,7 @@ describe Puppet::Type.type(:cron), :unless => Puppet.features.microsoft_windows? it "should accept the value '#{value}' for special" do expect { described_class.new(:name => 'foo', :minute => :absent, :special => value ) - }.to_not raise_error(Puppet::Error, /cannot specify both a special schedule and a value/) + }.to_not raise_error end } end @@ -477,7 +477,7 @@ describe Puppet::Type.type(:cron), :unless => Puppet.features.microsoft_windows? it "should accept the 'absent' value for special" do expect { described_class.new(:name => 'foo', :minute => "1", :special => :absent ) - }.to_not raise_error(Puppet::Error, /cannot specify both a special schedule and a value/) + }.to_not raise_error end end end diff --git a/spec/unit/type/exec_spec.rb b/spec/unit/type/exec_spec.rb index 6d780b3ff..ec9847e91 100755 --- a/spec/unit/type/exec_spec.rb +++ b/spec/unit/type/exec_spec.rb @@ -204,6 +204,15 @@ describe Puppet::Type.type(:exec) do }.to raise_error Puppet::Error, /Parameter user failed/ end + it "accepts the current user" do + Puppet.features.stubs(:root?).returns(false) + Etc.stubs(:getpwuid).returns(Struct::Passwd.new('input')) + + type = Puppet::Type.type(:exec).new(:name => '/bin/true whatever', :user => 'input') + + expect(type[:user]).to eq('input') + end + ['one', 2, 'root', 4294967295, 4294967296].each do |value| it "should accept '#{value}' as user if we are root" do Puppet.features.stubs(:root?).returns(true) diff --git a/spec/unit/type/file/content_spec.rb b/spec/unit/type/file/content_spec.rb index 33e995416..bf438d680 100755 --- a/spec/unit/type/file/content_spec.rb +++ b/spec/unit/type/file/content_spec.rb @@ -332,7 +332,7 @@ describe Puppet::Type.type(:file).attrclass(:content), :uses_checksums => true d end describe "from local source" do - let(:source_content) { "source file content\r\n"*10000 } + let(:source_content) { "source file content\r\n"*10 } before(:each) do sourcename = tmpfile('source') resource[:backup] = false @@ -362,104 +362,87 @@ describe Puppet::Type.type(:file).attrclass(:content), :uses_checksums => true d end end - describe "from an explicit fileserver" do - let(:source_content) { "source file content\n"*10000 } - let(:response) { stub_everything 'response' } + describe 'from remote source' do + let(:source_content) { "source file content\n"*10 } let(:source) { resource.newattr(:source) } + let(:response) { stub_everything('response') } + let(:conn) { mock('connection') } before(:each) do resource[:backup] = false - response.stubs(:read_body).multiple_yields(*(["source file content\n"]*10000)) - - conn = mock('connection') - conn.stubs(:request_get).yields response - - Puppet::Network::HttpPool.expects(:http_instance).with('somehostname',any_parameters).returns(conn).at_least_once - # This needs to be invoked to properly initialize the content property, # or attempting to write a file will fail. resource.newattr(:content) - source.stubs(:metadata).returns stub_everything('metadata', :source => "puppet://somehostname/test/foo", :ftype => 'file') + response.stubs(:read_body).multiple_yields(*source_content.lines) + conn.stubs(:request_get).yields(response) end - describe "and the request was successful" do - before { response.stubs(:code).returns '200' } - - it "should write the contents to the file" do - resource.write(source) - Puppet::FileSystem.binread(filename).should == source_content - end + it 'should use an explicit fileserver if source starts with puppet://' do + response.stubs(:code).returns('200') + source.stubs(:metadata).returns stub_everything('metadata', :source => 'puppet://somehostname/test/foo', :ftype => 'file') + Puppet::Network::HttpPool.expects(:http_instance).with('somehostname', anything).returns(conn) - with_digest_algorithms do - it "should return the checksum computed" do - File.open(filename, 'w') do |file| - resource[:checksum] = digest_algorithm - content.write(file).should == "{#{digest_algorithm}}#{digest(source_content)}" - end - end - end + resource.write(source) end - it "should not write anything if source is not found" do - response.stubs(:code).returns("404") - expect { resource.write(source) }.to raise_error(Net::HTTPError, /404/) - File.read(filename).should == "initial file content" - end + it 'should use the default fileserver if source starts with puppet:///' do + response.stubs(:code).returns('200') + source.stubs(:metadata).returns stub_everything('metadata', :source => 'puppet:///test/foo', :ftype => 'file') + Puppet::Network::HttpPool.expects(:http_instance).with(Puppet.settings[:server], anything).returns(conn) - it "should raise an HTTP error in case of server error" do - response.stubs(:code).returns("500") - expect { content.write(fh) }.to raise_error(Net::HTTPError, /500/) + resource.write(source) end - end + it 'should percent encode reserved characters' do + response.stubs(:code).returns('200') + Puppet::Network::HttpPool.stubs(:http_instance).returns(conn) + source.stubs(:metadata).returns stub_everything('metadata', :source => 'puppet:///test/foo bar', :ftype => 'file') - describe "from remote source" do - let(:source_content) { "source file content\n"*10000 } - let(:response) { stub_everything 'response' } - let(:source) { resource.newattr(:source) } + conn.unstub(:request_get) + conn.expects(:request_get).with('/none/file_content/test/foo%20bar', anything).yields(response) - before(:each) do - resource[:backup] = false - response.stubs(:read_body).multiple_yields(*(["source file content\n"]*10000)) + resource.write(source) + end - conn = stub_everything 'connection' - conn.stubs(:request_get).yields response - Puppet::Network::HttpPool.stubs(:http_instance).returns conn + describe 'when handling file_content responses' do + before(:each) do + Puppet::Network::HttpPool.stubs(:http_instance).returns(conn) + source.stubs(:metadata).returns stub_everything('metadata', :source => 'puppet:///test/foo', :ftype => 'file') + end - # This needs to be invoked to properly initialize the content property, - # or attempting to write a file will fail. - resource.newattr(:content) - source.stubs(:metadata).returns stub_everything('metadata', :source => "puppet://somehostname/test/foo", :ftype => 'file') - end + it 'should not write anything if source is not found' do + response.stubs(:code).returns('404') + + expect { resource.write(source) }.to raise_error(Net::HTTPError, /404/) + expect(File.read(filename)).to eq('initial file content') + end - describe "and the request was successful" do - before { response.stubs(:code).returns '200' } + it 'should raise an HTTP error in case of server error' do + response.stubs(:code).returns('500') - it "should write the contents to the file" do - resource.write(source) - Puppet::FileSystem.binread(filename).should == source_content + expect { resource.write(source) }.to raise_error(Net::HTTPError, /500/) end - with_digest_algorithms do - it "should return the checksum computed" do - File.open(filename, 'w') do |file| - resource[:checksum] = digest_algorithm - content.write(file).should == "{#{digest_algorithm}}#{digest(source_content)}" + context 'and the request was successful' do + before(:each) { response.stubs(:code).returns '200' } + + it 'should write the contents to the file' do + resource.write(source) + expect(Puppet::FileSystem.binread(filename)).to eq(source_content) + end + + with_digest_algorithms do + it 'should return the checksum computed' do + File.open(filename, 'w') do |file| + resource[:checksum] = digest_algorithm + expect(content.write(file)).to eq("{#{digest_algorithm}}#{digest(source_content)}") + end end end - end - end - it "should not write anything if source is not found" do - response.stubs(:code).returns("404") - expect {resource.write(source)}.to raise_error(Net::HTTPError, /404/) - File.read(filename).should == "initial file content" - end + end - it "should raise an HTTP error in case of server error" do - response.stubs(:code).returns("500") - expect { content.write(fh) }.to raise_error(Net::HTTPError, /500/) end end diff --git a/spec/unit/type/file/mode_spec.rb b/spec/unit/type/file/mode_spec.rb index 9936ebdbc..82bd5a09f 100755 --- a/spec/unit/type/file/mode_spec.rb +++ b/spec/unit/type/file/mode_spec.rb @@ -6,7 +6,7 @@ describe Puppet::Type.type(:file).attrclass(:mode) do include PuppetSpec::Files let(:path) { tmpfile('mode_spec') } - let(:resource) { Puppet::Type.type(:file).new :path => path, :mode => 0644 } + let(:resource) { Puppet::Type.type(:file).new :path => path, :mode => '0644' } let(:mode) { resource.property(:mode) } describe "#validate" do @@ -192,4 +192,29 @@ describe Puppet::Type.type(:file).attrclass(:mode) do (stat.mode & 0777).to_s(8).should == "644" end end + + describe '#sync with a symbolic mode of +X for a file' do + let(:resource_sym) { Puppet::Type.type(:file).new :path => path, :mode => 'g+wX' } + let(:mode_sym) { resource_sym.property(:mode) } + + before { FileUtils.touch(path) } + + it 'does not change executable bit if no executable bit is set' do + Puppet::FileSystem.chmod(0644, path) + + mode_sym.sync + + stat = Puppet::FileSystem.stat(path) + (stat.mode & 0777).to_s(8).should == '664' + end + + it 'does change executable bit if an executable bit is set' do + Puppet::FileSystem.chmod(0744, path) + + mode_sym.sync + + stat = Puppet::FileSystem.stat(path) + (stat.mode & 0777).to_s(8).should == '774' + end + end end diff --git a/spec/unit/type/file/source_spec.rb b/spec/unit/type/file/source_spec.rb index b6e97cd7a..ff192a5f4 100755 --- a/spec/unit/type/file/source_spec.rb +++ b/spec/unit/type/file/source_spec.rb @@ -176,7 +176,7 @@ describe Puppet::Type.type(:file).attrclass(:source) do end describe "when copying the source values" do - before do + before :each do @resource = Puppet::Type.type(:file).new :path => @foobar @source = source.new(:resource => @resource) @@ -186,6 +186,28 @@ describe Puppet::Type.type(:file).attrclass(:source) do Puppet.features.stubs(:root?).returns true end + it "should not issue a deprecation warning if the source mode value is a Numeric" do + @metadata.stubs(:mode).returns 0173 + if Puppet::Util::Platform.windows? + Puppet.expects(:deprecation_warning).with(regexp_matches(/Copying owner\/mode\/group from the source file on Windows is deprecated/)).at_least_once + else + Puppet.expects(:deprecation_warning).never + end + + @source.copy_source_values + end + + it "should not issue a deprecation warning if the source mode value is a String" do + @metadata.stubs(:mode).returns "173" + if Puppet::Util::Platform.windows? + Puppet.expects(:deprecation_warning).with(regexp_matches(/Copying owner\/mode\/group from the source file on Windows is deprecated/)).at_least_once + else + Puppet.expects(:deprecation_warning).never + end + + @source.copy_source_values + end + it "should fail if there is no metadata" do @source.stubs(:metadata).returns nil @source.expects(:devfail).raises ArgumentError @@ -409,7 +431,7 @@ describe Puppet::Type.type(:file).attrclass(:source) do @source.stubs(:local?).returns false Puppet.expects(:deprecation_warning).with(deprecation_message).at_least_once @resource[:group] = 2 - @resource[:mode] = 3 + @resource[:mode] = "0003" @source.copy_source_values end @@ -418,7 +440,7 @@ describe Puppet::Type.type(:file).attrclass(:source) do @source.stubs(:local?).returns false Puppet.expects(:deprecation_warning).with(deprecation_message).at_least_once @resource[:owner] = 1 - @resource[:mode] = 3 + @resource[:mode] = "0003" @source.copy_source_values end @@ -437,7 +459,7 @@ describe Puppet::Type.type(:file).attrclass(:source) do Puppet.expects(:deprecation_warning).with(deprecation_message).never @resource[:owner] = 1 @resource[:group] = 2 - @resource[:mode] = 3 + @resource[:mode] = "0003" @source.copy_source_values end diff --git a/spec/unit/type/file_spec.rb b/spec/unit/type/file_spec.rb index 00b027ed7..7a4d02553 100755 --- a/spec/unit/type/file_spec.rb +++ b/spec/unit/type/file_spec.rb @@ -418,7 +418,7 @@ describe Puppet::Type.type(:file) do it "should not copy values to the child which were set by the source" do source = File.expand_path(__FILE__) file[:source] = source - metadata = stub 'metadata', :owner => "root", :group => "root", :mode => 0755, :ftype => "file", :checksum => "{md5}whatever", :source => source + metadata = stub 'metadata', :owner => "root", :group => "root", :mode => '0755', :ftype => "file", :checksum => "{md5}whatever", :source => source file.parameter(:source).stubs(:metadata).returns metadata file.parameter(:source).copy_source_values @@ -1357,13 +1357,13 @@ describe Puppet::Type.type(:file) do target = described_class.new( :ensure => :file, :path => @target, :catalog => catalog, :content => 'yayness', - :mode => 0644) + :mode => '0644') catalog.add_resource target @link_resource = described_class.new( :ensure => :link, :path => @link, :target => @target, :catalog => catalog, - :mode => 0755) + :mode => '0755') catalog.add_resource @link_resource # to prevent the catalog from trying to write state.yaml diff --git a/spec/unit/type/nagios_spec.rb b/spec/unit/type/nagios_spec.rb index bc96c26d4..4bd1271c2 100755 --- a/spec/unit/type/nagios_spec.rb +++ b/spec/unit/type/nagios_spec.rb @@ -125,7 +125,7 @@ EOL parser = Nagios::Parser.new expect { results = parser.parse(ESCAPED_SEMICOLON) - }.to_not raise_error Nagios::Parser::SyntaxError + }.to_not raise_error end it "should ignore it if it is a comment" do @@ -147,7 +147,7 @@ EOL parser = Nagios::Parser.new expect { results = parser.parse(POUND_SIGN_HASH_SYMBOL_NOT_IN_FIRST_COLUMN) - }.to_not raise_error Nagios::Parser::SyntaxError + }.to_not raise_error end @@ -170,7 +170,7 @@ EOL parser = Nagios::Parser.new expect { results = parser.parse(ANOTHER_ESCAPED_SEMICOLON) - }.to_not raise_error Nagios::Parser::SyntaxError + }.to_not raise_error end it "should parse correctly" do @@ -217,6 +217,15 @@ describe "Nagios generator" do results = parser.parse(nagios_type.to_s) results[0].command_line.should eql(param) end + + it "should accept FixNum params and convert to string" do + param = 1 + nagios_type = Nagios::Base.create(:serviceescalation) + nagios_type.first_notification = param + parser = Nagios::Parser.new + results = parser.parse(nagios_type.to_s) + results[0].first_notification.should eql(param.to_s) + end end describe "Nagios resource types" do diff --git a/spec/unit/type/resources_spec.rb b/spec/unit/type/resources_spec.rb index f08afd7ae..e985b9752 100755 --- a/spec/unit/type/resources_spec.rb +++ b/spec/unit/type/resources_spec.rb @@ -5,6 +5,11 @@ resources = Puppet::Type.type(:resources) # There are still plenty of tests to port over from test/. describe resources do + + before :each do + described_class.reset_system_users_max_uid! + end + describe "when initializing" do it "should fail if the specified resource type does not exist" do Puppet::Type.stubs(:type).with { |x| x.to_s.downcase == "resources"}.returns resources @@ -47,7 +52,7 @@ describe resources do it "can be set to true for a resource type that has instances and can accept ensure" do instance.resource_type.stubs(:respond_to?).returns true instance.resource_type.stubs(:validproperty?).returns true - expect { instance[:purge] = 'yes' }.not_to raise_error Puppet::Error + expect { instance[:purge] = 'yes' }.to_not raise_error end end @@ -56,6 +61,7 @@ describe resources do before do @res = Puppet::Type.type(:resources).new :name => :user, :purge => true, :unless_system_user => true @res.catalog = Puppet::Resource::Catalog.new + Puppet::FileSystem.stubs(:exist?).with('/etc/login.defs').returns false end it "should never purge hardcoded system users" do @@ -71,60 +77,89 @@ describe resources do @res.user_check(user).should be_false end - it "should purge manual users if unless_system_user => true" do - user_hash = {:name => 'system_user', :uid => 525, :system => true} + it "should purge non-system users if unless_system_user => true" do + user_hash = {:name => 'system_user', :uid => described_class.system_users_max_uid + 1, :system => true} user = Puppet::Type.type(:user).new(user_hash) user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash) @res.user_check(user).should be_true end - it "should purge system users over 500 if unless_system_user => 600" do + it "should not purge system users under 600 if unless_system_user => 600" do res = Puppet::Type.type(:resources).new :name => :user, :purge => true, :unless_system_user => 600 res.catalog = Puppet::Resource::Catalog.new - user_hash = {:name => 'system_user', :uid => 525, :system => true} + user_hash = {:name => 'system_user', :uid => 500, :system => true} user = Puppet::Type.type(:user).new(user_hash) user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash) res.user_check(user).should be_false end end - describe "with unless_uid" do - describe "with a uid range" do - before do - @res = Puppet::Type.type(:resources).new :name => :user, :purge => true, :unless_uid => 10_000..20_000 + %w(FreeBSD OpenBSD).each do |os| + describe "on #{os}" do + before :each do + Facter.stubs(:value).with(:kernel).returns(os) + Facter.stubs(:value).with(:operatingsystem).returns(os) + Facter.stubs(:value).with(:osfamily).returns(os) + Puppet::FileSystem.stubs(:exist?).with('/etc/login.defs').returns false + @res = Puppet::Type.type(:resources).new :name => :user, :purge => true, :unless_system_user => true @res.catalog = Puppet::Resource::Catalog.new end - it "should purge uids that are not in a specified range" do - user_hash = {:name => 'special_user', :uid => 25_000} + it "should not purge system users under 1000" do + user_hash = {:name => 'system_user', :uid => 999} user = Puppet::Type.type(:user).new(user_hash) user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash) - @res.user_check(user).should be_true + @res.user_check(user).should be_false end - it "should not purge uids that are in a specified range" do - user_hash = {:name => 'special_user', :uid => 15_000} + it "should purge users over 999" do + user_hash = {:name => 'system_user', :uid => 1000} user = Puppet::Type.type(:user).new(user_hash) user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash) - @res.user_check(user).should be_false + @res.user_check(user).should be_true end end + end + + describe 'with login.defs present' do + before :each do + Puppet::FileSystem.expects(:exist?).with('/etc/login.defs').returns true + Puppet::FileSystem.expects(:each_line).with('/etc/login.defs').yields(' UID_MIN 1234 # UID_MIN comment ') + @res = Puppet::Type.type(:resources).new :name => :user, :purge => true, :unless_system_user => true + @res.catalog = Puppet::Resource::Catalog.new + end + + it 'should not purge a system user' do + user_hash = {:name => 'system_user', :uid => 1233} + user = Puppet::Type.type(:user).new(user_hash) + user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash) + @res.user_check(user).should be_false + end + + it 'should purge a non-system user' do + user_hash = {:name => 'system_user', :uid => 1234} + user = Puppet::Type.type(:user).new(user_hash) + user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash) + @res.user_check(user).should be_true + end + end - describe "with a uid range array" do + describe "with unless_uid" do + describe "with a uid array" do before do - @res = Puppet::Type.type(:resources).new :name => :user, :purge => true, :unless_uid => [10_000..15_000, 15_000..20_000] + @res = Puppet::Type.type(:resources).new :name => :user, :purge => true, :unless_uid => [15_000, 15_001, 15_002] @res.catalog = Puppet::Resource::Catalog.new end - it "should purge uids that are not in a specified range array" do + it "should purge uids that are not in a specified array" do user_hash = {:name => 'special_user', :uid => 25_000} user = Puppet::Type.type(:user).new(user_hash) user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash) @res.user_check(user).should be_true end - it "should not purge uids that are in a specified range array" do - user_hash = {:name => 'special_user', :uid => 15_000} + it "should not purge uids that are in a specified array" do + user_hash = {:name => 'special_user', :uid => 15000} user = Puppet::Type.type(:user).new(user_hash) user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash) @res.user_check(user).should be_false @@ -132,31 +167,30 @@ describe resources do end - describe "with a uid array" do + describe "with a single integer uid" do before do - @res = Puppet::Type.type(:resources).new :name => :user, :purge => true, :unless_uid => [15_000, 15_001, 15_002] + @res = Puppet::Type.type(:resources).new :name => :user, :purge => true, :unless_uid => 15_000 @res.catalog = Puppet::Resource::Catalog.new end - it "should purge uids that are not in a specified array" do + it "should purge uids that are not specified" do user_hash = {:name => 'special_user', :uid => 25_000} user = Puppet::Type.type(:user).new(user_hash) user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash) @res.user_check(user).should be_true end - it "should not purge uids that are in a specified array" do - user_hash = {:name => 'special_user', :uid => 15000} + it "should not purge uids that are specified" do + user_hash = {:name => 'special_user', :uid => 15_000} user = Puppet::Type.type(:user).new(user_hash) user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash) @res.user_check(user).should be_false end - end - describe "with a single uid" do + describe "with a single string uid" do before do - @res = Puppet::Type.type(:resources).new :name => :user, :purge => true, :unless_uid => 15_000 + @res = Puppet::Type.type(:resources).new :name => :user, :purge => true, :unless_uid => '15000' @res.catalog = Puppet::Resource::Catalog.new end @@ -177,7 +211,7 @@ describe resources do describe "with a mixed uid array" do before do - @res = Puppet::Type.type(:resources).new :name => :user, :purge => true, :unless_uid => [10_000..15_000, 16_666] + @res = Puppet::Type.type(:resources).new :name => :user, :purge => true, :unless_uid => ['15000', 16_666] @res.catalog = Puppet::Resource::Catalog.new end @@ -202,7 +236,7 @@ describe resources do @res.user_check(user).should be_true end end - + end end diff --git a/spec/unit/type/user_spec.rb b/spec/unit/type/user_spec.rb index d9468357b..f5a351752 100755 --- a/spec/unit/type/user_spec.rb +++ b/spec/unit/type/user_spec.rb @@ -467,40 +467,47 @@ describe Puppet::Type.type(:user) do res.catalog = Puppet::Resource::Catalog.new res end - it "should not just return from eval_generate" do + it "should not just return from generate" do subject.expects :find_unmanaged_keys - subject.eval_generate + subject.generate end it "should check each keyfile for readability" do paths.each do |path| File.expects(:readable?).with(path) end - subject.eval_generate + subject.generate end end describe "generated keys" do subject do - res = described_class.new(:name => "test", :purge_ssh_keys => purge_param) + res = described_class.new(:name => "test_user_name", :purge_ssh_keys => purge_param) res.catalog = Puppet::Resource::Catalog.new res end context "when purging is disabled" do let(:purge_param) { false } - its(:eval_generate) { should be_empty } + its(:generate) { should be_empty } end context "when purging is enabled" do let(:purge_param) { my_fixture('authorized_keys') } - let(:resources) { subject.eval_generate } + let(:resources) { subject.generate } it "should contain a resource for each key" do names = resources.collect { |res| res.name } - names.should include("keyname1") + names.should include("key1 name") names.should include("keyname2") end it "should not include keys in comment lines" do names = resources.collect { |res| res.name } names.should_not include("keyname3") end + it "should each have a value for the user property" do + resources.map { |res| + res[:user] + }.reject { |user_name| + user_name == "test_user_name" + }.should be_empty + end end end end diff --git a/spec/unit/type/yumrepo_spec.rb b/spec/unit/type/yumrepo_spec.rb index b97c60666..2246b7274 100644..100755 --- a/spec/unit/type/yumrepo_spec.rb +++ b/spec/unit/type/yumrepo_spec.rb @@ -10,6 +10,37 @@ shared_examples_for "a yumrepo parameter that can be absent" do |param| end end +shared_examples_for "a yumrepo parameter that expects a natural value" do |param| + it "accepts a valid positive integer" do + instance = described_class.new(:name => 'puppetlabs', param => '12') + expect(instance[param]).to eq '12' + end + it "rejects invalid negative integer" do + expect { + described_class.new( + :name => 'puppetlabs', + param => '-12' + ) + }.to raise_error(Puppet::ResourceError, /Parameter #{param} failed/) + end + it "rejects invalid non-integer" do + expect { + described_class.new( + :name => 'puppetlabs', + param => 'I\'m a six' + ) + }.to raise_error(Puppet::ResourceError, /Parameter #{param} failed/) + end + it "rejects invalid string with integers inside" do + expect { + described_class.new( + :name => 'puppetlabs', + param => 'I\'m a 6' + ) + }.to raise_error(Puppet::ResourceError, /Parameter #{param} failed/) + end +end + shared_examples_for "a yumrepo parameter that expects a boolean parameter" do |param| valid_values = %w[True False 0 1 No Yes] @@ -22,6 +53,14 @@ shared_examples_for "a yumrepo parameter that expects a boolean parameter" do |p instance = described_class.new(:name => 'puppetlabs', param => value.downcase) expect(instance[param]).to eq value.downcase end + it "fails on valid value #{value} contained in another value" do + expect { + described_class.new( + :name => 'puppetlabs', + param => "bla#{value}bla" + ) + }.to raise_error(Puppet::ResourceError, /Parameter #{param} failed/) + end end it "rejects invalid boolean values" do @@ -76,6 +115,26 @@ shared_examples_for "a yumrepo parameter that accepts multiple URLs" do |param| end end +shared_examples_for "a yumrepo parameter that accepts kMG units" do |param| + %w[k M G].each do |unit| + it "can accept an integer with #{unit} units" do + described_class.new( + :name => 'puppetlabs', + param => "123#{unit}" + ) + end + end + + it "fails if wrong unit passed" do + expect { + described_class.new( + :name => 'puppetlabs', + param => '123J' + ) + }.to raise_error(Puppet::ResourceError, /Parameter #{param} failed/) + end +end + describe Puppet::Type.type(:yumrepo) do it "has :name as its namevar" do expect(described_class.key_attributes).to eq [:name] @@ -154,6 +213,14 @@ describe Puppet::Type.type(:yumrepo) do it "accepts a value of #{value}" do described_class.new(:name => "puppetlabs", :failovermethod => value) end + it "fails on valid value #{value} contained in another value" do + expect { + described_class.new( + :name => 'puppetlabs', + :failovermethod => "bla#{value}bla" + ) + }.to raise_error(Puppet::ResourceError, /Parameter failovermethod failed/) + end end it "raises an error if an invalid value is given" do @@ -175,6 +242,14 @@ describe Puppet::Type.type(:yumrepo) do it "accepts a valid value of #{value}" do described_class.new(:name => 'puppetlabs', :http_caching => value) end + it "fails on valid value #{value} contained in another value" do + expect { + described_class.new( + :name => 'puppetlabs', + :http_caching => "bla#{value}bla" + ) + }.to raise_error(Puppet::ResourceError, /Parameter http_caching failed/) + end end it "rejects invalid values" do @@ -188,10 +263,25 @@ describe Puppet::Type.type(:yumrepo) do describe "timeout" do it_behaves_like "a yumrepo parameter that can be absent", :timeout + it_behaves_like "a yumrepo parameter that expects a natural value", :timeout end describe "metadata_expire" do it_behaves_like "a yumrepo parameter that can be absent", :metadata_expire + it_behaves_like "a yumrepo parameter that expects a natural value", :metadata_expire + + it "accepts dhm units" do + %W[d h m].each do |unit| + described_class.new( + :name => 'puppetlabs', + :metadata_expire => "123#{unit}" + ) + end + end + + it "accepts never as value" do + described_class.new(:name => 'puppetlabs', :metadata_expire => 'never') + end end describe "protect" do @@ -205,6 +295,12 @@ describe Puppet::Type.type(:yumrepo) do describe "proxy" do it_behaves_like "a yumrepo parameter that can be absent", :proxy + it "accepts _none_" do + described_class.new( + :name => 'puppetlabs', + :proxy => "_none_" + ) + end it_behaves_like "a yumrepo parameter that accepts a single URL", :proxy end @@ -247,5 +343,45 @@ describe Puppet::Type.type(:yumrepo) do it_behaves_like "a yumrepo parameter that can be absent", :metalink it_behaves_like "a yumrepo parameter that accepts a single URL", :metalink end + + + describe "cost" do + it_behaves_like "a yumrepo parameter that can be absent", :cost + it_behaves_like "a yumrepo parameter that expects a natural value", :cost + end + + describe "throttle" do + it_behaves_like "a yumrepo parameter that can be absent", :throttle + it_behaves_like "a yumrepo parameter that expects a natural value", :throttle + it_behaves_like "a yumrepo parameter that accepts kMG units", :throttle + + it "accepts percentage as unit" do + described_class.new( + :name => 'puppetlabs', + :throttle => '123%' + ) + end + end + + describe "bandwidth" do + it_behaves_like "a yumrepo parameter that can be absent", :bandwidth + it_behaves_like "a yumrepo parameter that expects a natural value", :bandwidth + it_behaves_like "a yumrepo parameter that accepts kMG units", :bandwidth + end + + describe "gpgcakey" do + it_behaves_like "a yumrepo parameter that can be absent", :gpgcakey + it_behaves_like "a yumrepo parameter that accepts a single URL", :gpgcakey + end + + describe "retries" do + it_behaves_like "a yumrepo parameter that can be absent", :retries + it_behaves_like "a yumrepo parameter that expects a natural value", :retries + end + + describe "mirrorlist_expire" do + it_behaves_like "a yumrepo parameter that can be absent", :mirrorlist_expire + it_behaves_like "a yumrepo parameter that expects a natural value", :mirrorlist_expire + end end end diff --git a/spec/unit/type/zone_spec.rb b/spec/unit/type/zone_spec.rb index e54902627..3497ae3a7 100755 --- a/spec/unit/type/zone_spec.rb +++ b/spec/unit/type/zone_spec.rb @@ -2,8 +2,11 @@ require 'spec_helper' describe Puppet::Type.type(:zone) do - let(:zone) { described_class.new(:name => 'dummy', :path => '/dummy', :provider => :solaris) } + let(:zone) { described_class.new(:name => 'dummy', :path => '/dummy', :provider => :solaris, :ip=>'if:1.2.3.4:2.3.4.5', :inherit=>'/', :dataset=>'tank') } let(:provider) { zone.provider } + let(:ip) { zone.property(:ip) } + let(:inherit) { zone.property(:inherit) } + let(:dataset) { zone.property(:dataset) } parameters = [:create_args, :install_args, :sysidcfg, :realhostname] @@ -21,6 +24,46 @@ describe Puppet::Type.type(:zone) do end end + describe "when trying to set a property that is empty" do + it "should verify that property.insync? of nil or :absent is true" do + [inherit, ip, dataset].each do |prop| + prop.stubs(:should).returns [] + end + [inherit, ip, dataset].each do |prop| + prop.insync?(nil).should be_true + end + [inherit, ip, dataset].each do |prop| + prop.insync?(:absent).should be_true + end + end + end + describe "when trying to set a property that is non empty" do + it "should verify that property.insync? of nil or :absent is false" do + [inherit, ip, dataset].each do |prop| + prop.stubs(:should).returns ['a','b'] + end + [inherit, ip, dataset].each do |prop| + prop.insync?(nil).should be_false + end + [inherit, ip, dataset].each do |prop| + prop.insync?(:absent).should be_false + end + end + end + describe "when trying to set a property that is non empty" do + it "insync? should return true or false depending on the current value, and new value" do + [inherit, ip, dataset].each do |prop| + prop.stubs(:should).returns ['a','b'] + end + [inherit, ip, dataset].each do |prop| + prop.insync?(['b', 'a']).should be_true + end + [inherit, ip, dataset].each do |prop| + prop.insync?(['a']).should be_false + end + end + end + it "should be valid when only :path is given" do described_class.new(:name => "dummy", :path => '/dummy', :provider => :solaris) end diff --git a/spec/unit/type_spec.rb b/spec/unit/type_spec.rb index 75a5a0768..8c250ee34 100755 --- a/spec/unit/type_spec.rb +++ b/spec/unit/type_spec.rb @@ -328,6 +328,26 @@ describe Puppet::Type, :unless => Puppet.features.microsoft_windows? do provider.ancestors.should include(Puppet::Provider) provider.should == @type.provider(:test_provider) end + + describe "with a parent class from another type" do + before :each do + @parent_type = Puppet::Type.newtype(:provider_parent_type) do + newparam(:name) { isnamevar } + end + @parent_provider = @parent_type.provide(:parent_provider) + end + + it "should be created successfully" do + child_provider = @type.provide(:child_provider, :parent => @parent_provider) + child_provider.ancestors.should include(@parent_provider) + end + + it "should be registered as a provider of the child type" do + child_provider = @type.provide(:child_provider, :parent => @parent_provider) + @type.providers.should include(:child_provider) + @parent_type.providers.should_not include(:child_provider) + end + end end describe "when choosing a default provider" do diff --git a/spec/unit/util/colors_spec.rb b/spec/unit/util/colors_spec.rb index 7407b628b..b0f791f82 100755 --- a/spec/unit/util/colors_spec.rb +++ b/spec/unit/util/colors_spec.rb @@ -67,17 +67,23 @@ describe Puppet::Util::Colors do end end - describe "on Windows", :if => Puppet.features.microsoft_windows? do - it "expects a trailing embedded NULL character in the wide string" do - message = "hello" + context "on Windows in Ruby 1.x", :if => Puppet.features.microsoft_windows? && RUBY_VERSION =~ /^1./ do + it "should define WideConsole" do + expect(defined?(Puppet::Util::Colors::WideConsole)).to be_true + end - console = Puppet::Util::Colors::WideConsole.new - wstr, nchars = console.string_encode(message) + it "should define WideIO" do + expect(defined?(Puppet::Util::Colors::WideIO)).to be_true + end + end - expect(nchars).to eq(message.length) + context "on Windows in Ruby 2.x", :if => Puppet.features.microsoft_windows? && RUBY_VERSION =~ /^2./ do + it "should not define WideConsole" do + expect(defined?(Puppet::Util::Colors::WideConsole)).to be_false + end - expect(wstr.length).to eq(nchars + 1) - expect(wstr[-1].ord).to be_zero + it "should not define WideIO" do + expect(defined?(Puppet::Util::Colors::WideIO)).to be_false end end end diff --git a/spec/unit/util/command_line_spec.rb b/spec/unit/util/command_line_spec.rb index 6ba8077c2..9eb61b077 100755 --- a/spec/unit/util/command_line_spec.rb +++ b/spec/unit/util/command_line_spec.rb @@ -70,7 +70,7 @@ describe Puppet::Util::CommandLine do it "should print the version and exit if #{arg} is given" do expect do described_class.new("puppet", [arg]).execute - end.to have_printed(/^#{Puppet.version}$/) + end.to have_printed(/^#{Regexp.escape(Puppet.version)}$/) end end end @@ -93,35 +93,39 @@ describe Puppet::Util::CommandLine do end describe "and an external implementation cannot be found" do + before :each do + Puppet::Util::CommandLine::UnknownSubcommand.any_instance.stubs(:console_has_color?).returns false + end + it "should abort and show the usage message" do - commandline = Puppet::Util::CommandLine.new("puppet", ['whatever', 'argument']) Puppet::Util.expects(:which).with('puppet-whatever').returns(nil) + commandline = Puppet::Util::CommandLine.new("puppet", ['whatever', 'argument']) commandline.expects(:exec).never expect { commandline.execute - }.to have_printed(/Unknown Puppet subcommand 'whatever'/) + }.to have_printed(/Unknown Puppet subcommand 'whatever'/).and_exit_with(1) end it "should abort and show the help message" do - commandline = Puppet::Util::CommandLine.new("puppet", ['whatever', 'argument']) Puppet::Util.expects(:which).with('puppet-whatever').returns(nil) + commandline = Puppet::Util::CommandLine.new("puppet", ['whatever', 'argument']) commandline.expects(:exec).never expect { commandline.execute - }.to have_printed(/See 'puppet help' for help on available puppet subcommands/) + }.to have_printed(/See 'puppet help' for help on available puppet subcommands/).and_exit_with(1) end %w{--version -V}.each do |arg| it "should abort and display #{arg} information" do - commandline = Puppet::Util::CommandLine.new("puppet", ['whatever', arg]) Puppet::Util.expects(:which).with('puppet-whatever').returns(nil) + commandline = Puppet::Util::CommandLine.new("puppet", ['whatever', arg]) commandline.expects(:exec).never expect { commandline.execute - }.to have_printed(/^#{Puppet.version}$/) + }.to have_printed(%r[^#{Regexp.escape(Puppet.version)}$]).and_exit_with(1) end end end diff --git a/spec/unit/util/execution_spec.rb b/spec/unit/util/execution_spec.rb index 7c6238f9f..6a4bee490 100755 --- a/spec/unit/util/execution_spec.rb +++ b/spec/unit/util/execution_spec.rb @@ -1,15 +1,9 @@ #! /usr/bin/env ruby require 'spec_helper' +require 'puppet/file_system/uniquefile' describe Puppet::Util::Execution do include Puppet::Util::Execution - # utility method to help deal with some windows vs. unix differences - def process_status(exitstatus) - return exitstatus if Puppet.features.microsoft_windows? - - stub('child_status', :exitstatus => exitstatus) - end - # utility methods to help us test some private methods without being quite so verbose def call_exec_posix(command, arguments, stdin, stdout, stderr) Puppet::Util::Execution.send(:execute_posix, command, arguments, stdin, stdout, stderr) @@ -28,8 +22,8 @@ describe Puppet::Util::Execution do def stub_process_wait(exitstatus) if Puppet.features.microsoft_windows? Puppet::Util::Windows::Process.stubs(:wait_process).with(process_handle).returns(exitstatus) - Process.stubs(:CloseHandle).with(process_handle) - Process.stubs(:CloseHandle).with(thread_handle) + FFI::WIN32.stubs(:CloseHandle).with(process_handle) + FFI::WIN32.stubs(:CloseHandle).with(thread_handle) else Process.stubs(:waitpid2).with(pid).returns([pid, stub('child_status', :exitstatus => exitstatus)]) end @@ -52,7 +46,7 @@ describe Puppet::Util::Execution do $stderr.stubs(:reopen) @stdin = File.open(null_file, 'r') - @stdout = Tempfile.new('stdout') + @stdout = Puppet::FileSystem::Uniquefile.new('stdout') @stderr = File.open(null_file, 'w') # there is a danger here that ENV will be modified by exec_posix. Normally it would only affect the ENV @@ -132,7 +126,7 @@ describe Puppet::Util::Execution do stub_process_wait(0) @stdin = File.open(null_file, 'r') - @stdout = Tempfile.new('stdout') + @stdout = Puppet::FileSystem::Uniquefile.new('stdout') @stderr = File.open(null_file, 'w') end @@ -223,8 +217,8 @@ describe Puppet::Util::Execution do describe "when squelch is not set" do it "should set stdout to a temporary output file" do - outfile = Tempfile.new('stdout') - Tempfile.stubs(:new).returns(outfile) + outfile = Puppet::FileSystem::Uniquefile.new('stdout') + Puppet::FileSystem::Uniquefile.stubs(:new).returns(outfile) Puppet::Util::Execution.expects(executor).with do |_,_,_,stdout,_| stdout.path == outfile.path @@ -234,8 +228,8 @@ describe Puppet::Util::Execution do end it "should set stderr to the same file as stdout if combine is true" do - outfile = Tempfile.new('stdout') - Tempfile.stubs(:new).returns(outfile) + outfile = Puppet::FileSystem::Uniquefile.new('stdout') + Puppet::FileSystem::Uniquefile.stubs(:new).returns(outfile) Puppet::Util::Execution.expects(executor).with do |_,_,_,stdout,stderr| stdout.path == outfile.path and stderr.path == outfile.path @@ -245,8 +239,8 @@ describe Puppet::Util::Execution do end it "should set stderr to the null device if combine is false" do - outfile = Tempfile.new('stdout') - Tempfile.stubs(:new).returns(outfile) + outfile = Puppet::FileSystem::Uniquefile.new('stdout') + Puppet::FileSystem::Uniquefile.stubs(:new).returns(outfile) Puppet::Util::Execution.expects(executor).with do |_,_,_,stdout,stderr| stdout.path == outfile.path and stderr.path == null_file @@ -256,8 +250,8 @@ describe Puppet::Util::Execution do end it "should combine stdout and stderr if combine is true" do - outfile = Tempfile.new('stdout') - Tempfile.stubs(:new).returns(outfile) + outfile = Puppet::FileSystem::Uniquefile.new('stdout') + Puppet::FileSystem::Uniquefile.stubs(:new).returns(outfile) Puppet::Util::Execution.expects(executor).with do |_,_,_,stdout,stderr| stdout.path == outfile.path and stderr.path == outfile.path @@ -267,8 +261,8 @@ describe Puppet::Util::Execution do end it "should default combine to true when no options are specified" do - outfile = Tempfile.new('stdout') - Tempfile.stubs(:new).returns(outfile) + outfile = Puppet::FileSystem::Uniquefile.new('stdout') + Puppet::FileSystem::Uniquefile.stubs(:new).returns(outfile) Puppet::Util::Execution.expects(executor).with do |_,_,_,stdout,stderr| stdout.path == outfile.path and stderr.path == outfile.path @@ -278,8 +272,8 @@ describe Puppet::Util::Execution do end it "should default combine to false when options are specified, but combine is not" do - outfile = Tempfile.new('stdout') - Tempfile.stubs(:new).returns(outfile) + outfile = Puppet::FileSystem::Uniquefile.new('stdout') + Puppet::FileSystem::Uniquefile.stubs(:new).returns(outfile) Puppet::Util::Execution.expects(executor).with do |_,_,_,stdout,stderr| stdout.path == outfile.path and stderr.path == null_file @@ -289,8 +283,8 @@ describe Puppet::Util::Execution do end it "should default combine to false when an empty hash of options is specified" do - outfile = Tempfile.new('stdout') - Tempfile.stubs(:new).returns(outfile) + outfile = Puppet::FileSystem::Uniquefile.new('stdout') + Puppet::FileSystem::Uniquefile.stubs(:new).returns(outfile) Puppet::Util::Execution.expects(executor).with do |_,_,_,stdout,stderr| stdout.path == outfile.path and stderr.path == null_file @@ -306,8 +300,8 @@ 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') - Puppet::Util::Windows::Process.expects(:CloseHandle).with(thread_handle) - Puppet::Util::Windows::Process.expects(:CloseHandle).with(process_handle) + FFI::WIN32.expects(:CloseHandle).with(thread_handle) + FFI::WIN32.expects(:CloseHandle).with(process_handle) expect { Puppet::Util::Execution.execute('test command') }.to raise_error(RuntimeError) end @@ -507,25 +501,25 @@ describe Puppet::Util::Execution do end it "should read and return the output if squelch is false" do - stdout = Tempfile.new('test') - Tempfile.stubs(:new).returns(stdout) + stdout = Puppet::FileSystem::Uniquefile.new('test') + Puppet::FileSystem::Uniquefile.stubs(:new).returns(stdout) stdout.write("My expected command output") Puppet::Util::Execution.execute('test command').should == "My expected command output" end it "should not read the output if squelch is true" do - stdout = Tempfile.new('test') - Tempfile.stubs(:new).returns(stdout) + stdout = Puppet::FileSystem::Uniquefile.new('test') + Puppet::FileSystem::Uniquefile.stubs(:new).returns(stdout) stdout.write("My expected command output") Puppet::Util::Execution.execute('test command', :squelch => true).should == '' end it "should delete the file used for output if squelch is false" do - stdout = Tempfile.new('test') + stdout = Puppet::FileSystem::Uniquefile.new('test') path = stdout.path - Tempfile.stubs(:new).returns(stdout) + Puppet::FileSystem::Uniquefile.stubs(:new).returns(stdout) Puppet::Util::Execution.execute('test command') @@ -533,8 +527,8 @@ describe Puppet::Util::Execution do end it "should not raise an error if the file is open" do - stdout = Tempfile.new('test') - Tempfile.stubs(:new).returns(stdout) + stdout = Puppet::FileSystem::Uniquefile.new('test') + Puppet::FileSystem::Uniquefile.stubs(:new).returns(stdout) file = File.new(stdout.path, 'r') Puppet::Util.execute('test command') @@ -597,40 +591,39 @@ describe Puppet::Util::Execution do describe "#execpipe" do it "should execute a string as a string" do Puppet::Util::Execution.expects(:open).with('| echo hello 2>&1').returns('hello') - $CHILD_STATUS.expects(:==).with(0).returns(true) + Puppet::Util::Execution.expects(:exitstatus).returns(0) Puppet::Util::Execution.execpipe('echo hello').should == 'hello' end it "should print meaningful debug message for string argument" do Puppet::Util::Execution.expects(:debug).with("Executing 'echo hello'") Puppet::Util::Execution.expects(:open).with('| echo hello 2>&1').returns('hello') - $CHILD_STATUS.expects(:==).with(0).returns(true) + Puppet::Util::Execution.expects(:exitstatus).returns(0) Puppet::Util::Execution.execpipe('echo hello') end it "should print meaningful debug message for array argument" do Puppet::Util::Execution.expects(:debug).with("Executing 'echo hello'") Puppet::Util::Execution.expects(:open).with('| echo hello 2>&1').returns('hello') - $CHILD_STATUS.expects(:==).with(0).returns(true) + Puppet::Util::Execution.expects(:exitstatus).returns(0) Puppet::Util::Execution.execpipe(['echo','hello']) end it "should execute an array by pasting together with spaces" do Puppet::Util::Execution.expects(:open).with('| echo hello 2>&1').returns('hello') - $CHILD_STATUS.expects(:==).with(0).returns(true) + Puppet::Util::Execution.expects(:exitstatus).returns(0) Puppet::Util::Execution.execpipe(['echo', 'hello']).should == 'hello' end it "should fail if asked to fail, and the child does" do - Puppet::Util::Execution.stubs(:open).returns('error message') - $CHILD_STATUS.expects(:==).with(0).returns(false) + Puppet::Util::Execution.stubs(:open).with('| echo hello 2>&1').returns('error message') + Puppet::Util::Execution.expects(:exitstatus).returns(1) expect { Puppet::Util::Execution.execpipe('echo hello') }. to raise_error Puppet::ExecutionFailure, /error message/ end it "should not fail if asked not to fail, and the child does" do Puppet::Util::Execution.stubs(:open).returns('error message') - $CHILD_STATUS.stubs(:==).with(0).returns(false) Puppet::Util::Execution.execpipe('echo hello', false).should == 'error message' end end diff --git a/spec/unit/util/feature_spec.rb b/spec/unit/util/feature_spec.rb index aa8afbba6..e6d844533 100755 --- a/spec/unit/util/feature_spec.rb +++ b/spec/unit/util/feature_spec.rb @@ -91,4 +91,16 @@ describe Puppet::Util::Feature do @features.should_not be_myfeature @features.should be_myfeature end + + it "should cache load failures when configured to do so" do + Puppet[:always_cache_features] = true + + @features.add(:myfeature, :libs => %w{foo bar}) + @features.expects(:require).with("foo").raises(LoadError) + + @features.should_not be_myfeature + # second call would cause an expectation exception if 'require' was + # called a second time + @features.should_not be_myfeature + end end diff --git a/spec/unit/util/http_proxy_spec.rb b/spec/unit/util/http_proxy_spec.rb index bc6b4d2b7..59f39c511 100644 --- a/spec/unit/util/http_proxy_spec.rb +++ b/spec/unit/util/http_proxy_spec.rb @@ -4,7 +4,7 @@ require 'puppet/util/http_proxy' describe Puppet::Util::HttpProxy do - host, port = 'some.host', 1234 + host, port, user, password = 'some.host', 1234, 'user1', 'pAssw0rd' describe ".http_proxy_env" do it "should return nil if no environment variables" do @@ -80,4 +80,46 @@ describe Puppet::Util::HttpProxy do end + describe ".http_proxy_user" do + it "should return a proxy user if set in environment" do + Puppet::Util.withenv('HTTP_PROXY' => "http://#{user}:#{password}@#{host}:#{port}") do + subject.http_proxy_user.should == user + end + end + + it "should return a proxy user if set in config" do + Puppet.settings[:http_proxy_user] = user + subject.http_proxy_user.should == user + end + + it "should use environment variable before puppet settings" do + Puppet::Util.withenv('HTTP_PROXY' => "http://#{user}:#{password}@#{host}:#{port}") do + Puppet.settings[:http_proxy_user] = 'clownpants' + subject.http_proxy_user.should == user + end + end + + end + + describe ".http_proxy_password" do + it "should return a proxy password if set in environment" do + Puppet::Util.withenv('HTTP_PROXY' => "http://#{user}:#{password}@#{host}:#{port}") do + subject.http_proxy_password.should == password + end + end + + it "should return a proxy password if set in config" do + Puppet.settings[:http_proxy_user] = user + Puppet.settings[:http_proxy_password] = password + subject.http_proxy_password.should == password + end + + it "should use environment variable before puppet settings" do + Puppet::Util.withenv('HTTP_PROXY' => "http://#{user}:#{password}@#{host}:#{port}") do + Puppet.settings[:http_proxy_password] = 'clownpants' + subject.http_proxy_password.should == password + end + end + + end end diff --git a/spec/unit/util/log/destinations_spec.rb b/spec/unit/util/log/destinations_spec.rb index a91236dba..a81eac631 100755 --- a/spec/unit/util/log/destinations_spec.rb +++ b/spec/unit/util/log/destinations_spec.rb @@ -29,7 +29,7 @@ describe Puppet::Util::Log.desttypes[:file] do before do File.stubs(:open) # prevent actually creating the file - File.stubs(:chown) # prevent chown on non existing file from failing + File.stubs(:chown) # prevent chown on non existing file from failing @class = Puppet::Util::Log.desttypes[:file] end @@ -181,3 +181,47 @@ describe Puppet::Util::Log.desttypes[:console] do end end end + + +describe ":eventlog", :if => Puppet::Util::Platform.windows? do + before do + if Facter.value(:kernelmajversion).to_f < 6.0 + pending("requires win32-eventlog gem upgrade to 0.6.2 on Windows 2003") + end + end + + let(:klass) { Puppet::Util::Log.desttypes[:eventlog] } + + def expects_message_with_type(klass, level, eventlog_type, eventlog_id) + eventlog = stub('eventlog') + eventlog.expects(:report_event).with(has_entries(:source => "Puppet", :event_type => eventlog_type, :event_id => eventlog_id, :data => "a hitchhiker: don't panic")) + Win32::EventLog.stubs(:open).returns(eventlog) + + msg = Puppet::Util::Log.new(:level => level, :message => "don't panic", :source => "a hitchhiker") + dest = klass.new + dest.handle(msg) + end + + it "supports the eventlog feature" do + expect(Puppet.features.eventlog?).to be_true + end + + it "logs to the Application event log" do + eventlog = stub('eventlog') + Win32::EventLog.expects(:open).with('Application').returns(stub('eventlog')) + + klass.new + end + + it "logs :debug level as an information type event" do + expects_message_with_type(klass, :debug, klass::EVENTLOG_INFORMATION_TYPE, 0x1) + end + + it "logs :warning level as an warning type event" do + expects_message_with_type(klass, :warning, klass::EVENTLOG_WARNING_TYPE, 0x2) + end + + it "logs :err level as an error type event" do + expects_message_with_type(klass, :err, klass::EVENTLOG_ERROR_TYPE, 0x3) + end +end diff --git a/spec/unit/util/logging_spec.rb b/spec/unit/util/logging_spec.rb index 0858f7857..abdae9189 100755 --- a/spec/unit/util/logging_spec.rb +++ b/spec/unit/util/logging_spec.rb @@ -93,6 +93,12 @@ describe Puppet::Util::Logging do end describe "when sending a deprecation warning" do + it "does not log a message when deprecation warnings are disabled" do + Puppet.expects(:[]).with(:disable_warnings).returns %w[deprecations] + @logger.expects(:warning).never + @logger.deprecation_warning 'foo' + end + it "logs the message with warn" do @logger.expects(:warning).with do |msg| msg =~ /^foo\n/ @@ -133,6 +139,44 @@ describe Puppet::Util::Logging do end end + describe "when sending a puppet_deprecation_warning" do + it "requires file and line or key options" do + expect do + @logger.puppet_deprecation_warning("foo") + end.to raise_error(Puppet::DevError, /Need either :file and :line, or :key/) + expect do + @logger.puppet_deprecation_warning("foo", :file => 'bar') + end.to raise_error(Puppet::DevError, /Need either :file and :line, or :key/) + expect do + @logger.puppet_deprecation_warning("foo", :key => 'akey') + @logger.puppet_deprecation_warning("foo", :file => 'afile', :line => 1) + end.to_not raise_error + end + + it "warns with file and line" do + @logger.expects(:warning).with(regexp_matches(/deprecated foo.*afile:5/m)) + @logger.puppet_deprecation_warning("deprecated foo", :file => 'afile', :line => 5) + end + + it "warns keyed from file and line" do + @logger.expects(:warning).with(regexp_matches(/deprecated foo.*afile:5/m)).once + 5.times do + @logger.puppet_deprecation_warning("deprecated foo", :file => 'afile', :line => 5) + end + end + + it "warns with separate key only once regardless of file and line" do + @logger.expects(:warning).with(regexp_matches(/deprecated foo.*afile:5/m)).once + @logger.puppet_deprecation_warning("deprecated foo", :key => 'some_key', :file => 'afile', :line => 5) + @logger.puppet_deprecation_warning("deprecated foo", :key => 'some_key', :file => 'bfile', :line => 3) + end + + it "warns with key but no file and line" do + @logger.expects(:warning).with(regexp_matches(/deprecated foo.*unknown:unknown/m)) + @logger.puppet_deprecation_warning("deprecated foo", :key => 'some_key') + end + end + describe "when formatting exceptions" do it "should be able to format a chain of exceptions" do exc3 = Puppet::Error.new("original") diff --git a/spec/unit/util/pidlock_spec.rb b/spec/unit/util/pidlock_spec.rb index 2ebe7dec8..fcef7aa31 100644 --- a/spec/unit/util/pidlock_spec.rb +++ b/spec/unit/util/pidlock_spec.rb @@ -50,6 +50,26 @@ describe Puppet::Util::Pidlock do Puppet::FileSystem.exist?(@lockfile).should be_true end + it 'should create an empty lock file even when pid is missing' do + Process.stubs(:pid).returns('') + @lock.lock + Puppet::FileSystem.exist?(@lock.file_path).should be_true + Puppet::FileSystem.read(@lock.file_path).should be_empty + end + + it 'should replace an existing empty lockfile with a pid, given a subsequent lock call made against a valid pid' do + # empty pid results in empty lockfile + Process.stubs(:pid).returns('') + @lock.lock + Puppet::FileSystem.exist?(@lock.file_path).should be_true + + # next lock call with valid pid kills existing empty lockfile + Process.stubs(:pid).returns(1234) + @lock.lock + Puppet::FileSystem.exist?(@lock.file_path).should be_true + Puppet::FileSystem.read(@lock.file_path).should == '1234' + end + it "should expose the lock file_path" do @lock.file_path.should == @lockfile end @@ -83,6 +103,22 @@ describe Puppet::Util::Pidlock do @lock.lock @lock.should be_locked end + + it "should remove the lockfile when pid is missing" do + Process.stubs(:pid).returns('') + @lock.lock + @lock.locked?.should be_false + Puppet::FileSystem.exist?(@lock.file_path).should be_false + end + end + + describe '#lock_pid' do + it 'should return nil if the pid is empty' do + # fake pid to get empty lockfile + Process.stubs(:pid).returns('') + @lock.lock + @lock.lock_pid.should == nil + end end describe "with a stale lock" do @@ -105,7 +141,7 @@ describe Puppet::Util::Pidlock do describe "#lock" do it "should clear stale locks" do - @lock.locked? + @lock.locked?.should be_false Puppet::FileSystem.exist?(@lockfile).should be_false end diff --git a/spec/unit/util/profiler/aggregate_spec.rb b/spec/unit/util/profiler/aggregate_spec.rb new file mode 100644 index 000000000..8d38d4673 --- /dev/null +++ b/spec/unit/util/profiler/aggregate_spec.rb @@ -0,0 +1,59 @@ +require 'spec_helper' +require 'puppet/util/profiler' +require 'puppet/util/profiler/around_profiler' +require 'puppet/util/profiler/aggregate' + +describe Puppet::Util::Profiler::Aggregate do + let(:logger) { AggregateSimpleLog.new } + let(:profiler) { Puppet::Util::Profiler::Aggregate.new(logger, nil) } + let(:profiler_mgr) do + p = Puppet::Util::Profiler::AroundProfiler.new + p.add_profiler(profiler) + p + end + + it "tracks the aggregate counts and time for the hierarchy of metrics" do + profiler_mgr.profile("Looking up hiera data in production environment", ["function", "hiera_lookup", "production"]) { sleep 0.01 } + profiler_mgr.profile("Looking up hiera data in test environment", ["function", "hiera_lookup", "test"]) {} + profiler_mgr.profile("looking up stuff for compilation", ["compiler", "lookup"]) { sleep 0.01 } + profiler_mgr.profile("COMPILING ALL OF THE THINGS!", ["compiler", "compiling"]) {} + + profiler.values["function"].count.should == 2 + profiler.values["function"].time.should be > 0 + profiler.values["function"]["hiera_lookup"].count.should == 2 + profiler.values["function"]["hiera_lookup"]["production"].count.should == 1 + profiler.values["function"]["hiera_lookup"]["test"].count.should == 1 + profiler.values["function"].time.should be >= profiler.values["function"]["hiera_lookup"]["test"].time + + profiler.values["compiler"].count.should == 2 + profiler.values["compiler"].time.should be > 0 + profiler.values["compiler"]["lookup"].count.should == 1 + profiler.values["compiler"]["compiling"].count.should == 1 + profiler.values["compiler"].time.should be >= profiler.values["compiler"]["lookup"].time + + profiler.shutdown + + logger.output.should =~ /function -> hiera_lookup: .*\(2 calls\)\nfunction -> hiera_lookup ->.*\(1 calls\)/ + logger.output.should =~ /compiler: .*\(2 calls\)\ncompiler ->.*\(1 calls\)/ + end + + it "tolerates calls to `profile` that don't include a metric id" do + profiler_mgr.profile("yo") {} + end + + it "supports both symbols and strings as components of a metric id" do + profiler_mgr.profile("yo", [:foo, "bar"]) {} + end + + class AggregateSimpleLog + attr_reader :output + + def initialize + @output = "" + end + + def call(msg) + @output << msg << "\n" + end + end +end diff --git a/spec/unit/util/profiler/around_profiler_spec.rb b/spec/unit/util/profiler/around_profiler_spec.rb new file mode 100644 index 000000000..0837395b5 --- /dev/null +++ b/spec/unit/util/profiler/around_profiler_spec.rb @@ -0,0 +1,61 @@ +require 'spec_helper' +require 'puppet/util/profiler' + +describe Puppet::Util::Profiler::AroundProfiler do + let(:child) { TestAroundProfiler.new() } + let(:profiler) { Puppet::Util::Profiler::AroundProfiler.new } + + before :each do + profiler.add_profiler(child) + end + + it "returns the value of the profiled segment" do + retval = profiler.profile("Testing", ["testing"]) { "the return value" } + + retval.should == "the return value" + end + + it "propagates any errors raised in the profiled segment" do + expect do + profiler.profile("Testing", ["testing"]) { raise "a problem" } + end.to raise_error("a problem") + end + + it "makes the description and the context available to the `start` and `finish` methods" do + profiler.profile("Testing", ["testing"]) { } + + child.context.should == "Testing" + child.description.should == "Testing" + end + + it "calls finish even when an error is raised" do + begin + profiler.profile("Testing", ["testing"]) { raise "a problem" } + rescue + child.context.should == "Testing" + end + end + + it "supports multiple profilers" do + profiler2 = TestAroundProfiler.new + profiler.add_profiler(profiler2) + profiler.profile("Testing", ["testing"]) {} + + child.context.should == "Testing" + profiler2.context.should == "Testing" + end + + class TestAroundProfiler + attr_accessor :context, :description + + def start(description, metric_id) + description + end + + def finish(context, description, metric_id) + @context = context + @description = description + end + end +end + diff --git a/spec/unit/util/profiler/logging_spec.rb b/spec/unit/util/profiler/logging_spec.rb index 5316e5ae9..3f6a728dd 100644 --- a/spec/unit/util/profiler/logging_spec.rb +++ b/spec/unit/util/profiler/logging_spec.rb @@ -4,51 +4,40 @@ require 'puppet/util/profiler' describe Puppet::Util::Profiler::Logging do let(:logger) { SimpleLog.new } let(:identifier) { "Profiling ID" } - let(:profiler) { TestLoggingProfiler.new(logger, identifier) } - - it "returns the value of the profiled segment" do - retval = profiler.profile("Testing") { "the return value" } - - retval.should == "the return value" - end - - it "propogates any errors raised in the profiled segment" do - expect do - profiler.profile("Testing") { raise "a problem" } - end.to raise_error("a problem") + let(:logging_profiler) { TestLoggingProfiler.new(logger, identifier) } + let(:profiler) do + p = Puppet::Util::Profiler::AroundProfiler.new + p.add_profiler(logging_profiler) + p end it "logs the explanation of the profile results" do - profiler.profile("Testing") { } + profiler.profile("Testing", ["test"]) { } logger.messages.first.should =~ /the explanation/ end - it "logs results even when an error is raised" do - begin - profiler.profile("Testing") { raise "a problem" } - rescue - logger.messages.first.should =~ /the explanation/ - end - end - it "describes the profiled segment" do - profiler.profile("Tested measurement") { } + profiler.profile("Tested measurement", ["test"]) { } logger.messages.first.should =~ /PROFILE \[#{identifier}\] \d Tested measurement/ end it "indicates the order in which segments are profiled" do - profiler.profile("Measurement") { } - profiler.profile("Another measurement") { } + profiler.profile("Measurement", ["measurement"]) { } + profiler.profile("Another measurement", ["measurement"]) { } logger.messages[0].should =~ /1 Measurement/ logger.messages[1].should =~ /2 Another measurement/ end it "indicates the nesting of profiled segments" do - profiler.profile("Measurement") { profiler.profile("Nested measurement") { } } - profiler.profile("Another measurement") { profiler.profile("Another nested measurement") { } } + profiler.profile("Measurement", ["measurement1"]) do + profiler.profile("Nested measurement", ["measurement2"]) { } + end + profiler.profile("Another measurement", ["measurement1"]) do + profiler.profile("Another nested measurement", ["measurement2"]) { } + end logger.messages[0].should =~ /1.1 Nested measurement/ logger.messages[1].should =~ /1 Measurement/ @@ -57,12 +46,12 @@ describe Puppet::Util::Profiler::Logging do end class TestLoggingProfiler < Puppet::Util::Profiler::Logging - def start + def do_start(metric, description) "the start" end - def finish(context) - "the explanation of #{context}" + def do_finish(context, metric, description) + {:msg => "the explanation of #{context}"} end end diff --git a/spec/unit/util/profiler/none_spec.rb b/spec/unit/util/profiler/none_spec.rb deleted file mode 100644 index 0cabfef6f..000000000 --- a/spec/unit/util/profiler/none_spec.rb +++ /dev/null @@ -1,12 +0,0 @@ -require 'spec_helper' -require 'puppet/util/profiler' - -describe Puppet::Util::Profiler::None do - let(:profiler) { Puppet::Util::Profiler::None.new } - - it "returns the value of the profiled block" do - retval = profiler.profile("Testing") { "the return value" } - - retval.should == "the return value" - end -end diff --git a/spec/unit/util/profiler/wall_clock_spec.rb b/spec/unit/util/profiler/wall_clock_spec.rb index 668f63221..1adcf0d61 100644 --- a/spec/unit/util/profiler/wall_clock_spec.rb +++ b/spec/unit/util/profiler/wall_clock_spec.rb @@ -6,7 +6,7 @@ describe Puppet::Util::Profiler::WallClock do it "logs the number of seconds it took to execute the segment" do profiler = Puppet::Util::Profiler::WallClock.new(nil, nil) - message = profiler.finish(profiler.start) + message = profiler.do_finish(profiler.start(["foo", "bar"], "Testing"), ["foo", "bar"], "Testing")[:msg] message.should =~ /took \d\.\d{4} seconds/ end diff --git a/spec/unit/util/profiler_spec.rb b/spec/unit/util/profiler_spec.rb new file mode 100644 index 000000000..c7fc48cb9 --- /dev/null +++ b/spec/unit/util/profiler_spec.rb @@ -0,0 +1,55 @@ +require 'spec_helper' +require 'puppet/util/profiler' + +describe Puppet::Util::Profiler do + let(:profiler) { TestProfiler.new() } + + it "supports adding profilers" do + subject.add_profiler(profiler) + subject.current[0].should == profiler + end + + it "supports removing profilers" do + subject.add_profiler(profiler) + subject.remove_profiler(profiler) + subject.current.length.should == 0 + end + + it "supports clearing profiler list" do + subject.add_profiler(profiler) + subject.clear + subject.current.length.should == 0 + end + + it "supports profiling" do + subject.add_profiler(profiler) + subject.profile("hi", ["mymetric"]) {} + profiler.context[:metric_id].should == ["mymetric"] + profiler.context[:description].should == "hi" + profiler.description.should == "hi" + end + + it "supports profiling without a metric id" do + subject.add_profiler(profiler) + subject.profile("hi") {} + profiler.context[:metric_id].should == nil + profiler.context[:description].should == "hi" + profiler.description.should == "hi" + end + + class TestProfiler + attr_accessor :context, :metric, :description + + def start(description, metric_id) + {:metric_id => metric_id, + :description => description} + end + + def finish(context, description, metric_id) + @context = context + @metric_id = metric_id + @description = description + end + end +end + diff --git a/spec/unit/util/queue_spec.rb b/spec/unit/util/queue_spec.rb index d7ba57f85..48b98e8e3 100755 --- a/spec/unit/util/queue_spec.rb +++ b/spec/unit/util/queue_spec.rb @@ -1,7 +1,6 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/queue' -require 'spec/mocks' def make_test_client_class(n) c = Class.new do diff --git a/spec/unit/util/rdoc/parser_spec.rb b/spec/unit/util/rdoc/parser_spec.rb index acc606b76..7ed2cffcc 100755 --- a/spec/unit/util/rdoc/parser_spec.rb +++ b/spec/unit/util/rdoc/parser_spec.rb @@ -21,6 +21,17 @@ describe "RDoc::Parser", :if => Puppet.features.rdoc1? do end describe "when scanning files" do + around(:each) do |example| + Puppet.override({ + :current_environment => Puppet::Node::Environment.create(:doc, [], '/somewhere/etc/manifests/site.pp') + }, + "A fake current environment that the application would have established by now" + ) do + + example.run + end + end + it "should parse puppet files with the puppet parser" do @parser.stubs(:scan_top_level) parser = stub 'parser' @@ -56,15 +67,12 @@ describe "RDoc::Parser", :if => Puppet.features.rdoc1? do it "should scan the top level even if the file has already parsed" do known_type = stub 'known_types' - env = Puppet::Node::Environment.create(Puppet[:environment].to_sym, []) + env = Puppet.lookup(:current_environment) env.stubs(:known_resource_types).returns(known_type) known_type.expects(:watching_file?).with("module/manifests/init.pp").returns(true) - Puppet.override(:environments => Puppet::Environments::Static.new(env)) do - - @parser.expects(:scan_top_level) + @parser.expects(:scan_top_level) - @parser.scan - end + @parser.scan end end diff --git a/spec/unit/util/tagging_spec.rb b/spec/unit/util/tagging_spec.rb index 248e915e9..53bb39d7a 100755 --- a/spec/unit/util/tagging_spec.rb +++ b/spec/unit/util/tagging_spec.rb @@ -41,7 +41,7 @@ describe Puppet::Util::Tagging do end it "should allow tags containing '.' characters" do - expect { tagger.tag("good.tag") }.to_not raise_error(Puppet::ParseError) + expect { tagger.tag("good.tag") }.to_not raise_error end it "should add qualified classes as tags" do @@ -127,5 +127,36 @@ describe Puppet::Util::Tagging do expect(tagger).to be_tagged("two") expect(tagger).to be_tagged("three") end + + it "protects against empty tags" do + expect { tagger.tags = "one,,two"}.to raise_error(/Invalid tag ''/) + end + + it "takes an array of tags" do + tagger.tags = ["one", "two"] + + expect(tagger).to be_tagged("one") + expect(tagger).to be_tagged("two") + end + + it "removes any existing tags when reassigning" do + tagger.tags = "one, two" + + tagger.tags = "three, four" + + expect(tagger).to_not be_tagged("one") + expect(tagger).to_not be_tagged("two") + expect(tagger).to be_tagged("three") + expect(tagger).to be_tagged("four") + end + + it "allows empty tags that are generated from :: separated tags" do + tagger.tags = "one::::two::three" + + expect(tagger).to be_tagged("one") + expect(tagger).to be_tagged("") + expect(tagger).to be_tagged("two") + expect(tagger).to be_tagged("three") + end end end diff --git a/spec/unit/util/windows/access_control_entry_spec.rb b/spec/unit/util/windows/access_control_entry_spec.rb index b139b0d42..8d3f51c8a 100644 --- a/spec/unit/util/windows/access_control_entry_spec.rb +++ b/spec/unit/util/windows/access_control_entry_spec.rb @@ -5,7 +5,7 @@ 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 } + let(:mask) { Puppet::Util::Windows::File::FILE_ALL_ACCESS } it "creates an access allowed ace" do ace = klass.new(sid, mask) diff --git a/spec/unit/util/adsi_spec.rb b/spec/unit/util/windows/adsi_spec.rb index 491c4374b..f569d91e9 100755 --- a/spec/unit/util/adsi_spec.rb +++ b/spec/unit/util/windows/adsi_spec.rb @@ -2,97 +2,104 @@ require 'spec_helper' -require 'puppet/util/adsi' +require 'puppet/util/windows' -describe Puppet::Util::ADSI do +describe Puppet::Util::Windows::ADSI, :if => Puppet.features.microsoft_windows? do let(:connection) { stub 'connection' } before(:each) do - Puppet::Util::ADSI.instance_variable_set(:@computer_name, 'testcomputername') - Puppet::Util::ADSI.stubs(:connect).returns connection + Puppet::Util::Windows::ADSI.instance_variable_set(:@computer_name, 'testcomputername') + Puppet::Util::Windows::ADSI.stubs(:connect).returns connection end after(:each) do - Puppet::Util::ADSI.instance_variable_set(:@computer_name, nil) + Puppet::Util::Windows::ADSI.instance_variable_set(:@computer_name, nil) end it "should generate the correct URI for a resource" do - Puppet::Util::ADSI.uri('test', 'user').should == "WinNT://./test,user" + Puppet::Util::Windows::ADSI.uri('test', 'user').should == "WinNT://./test,user" end it "should be able to get the name of the computer" do - Puppet::Util::ADSI.computer_name.should == 'testcomputername' + Puppet::Util::Windows::ADSI.computer_name.should == 'testcomputername' end it "should be able to provide the correct WinNT base URI for the computer" do - Puppet::Util::ADSI.computer_uri.should == "WinNT://." + Puppet::Util::Windows::ADSI.computer_uri.should == "WinNT://." end it "should generate a fully qualified WinNT URI" do - Puppet::Util::ADSI.computer_uri('testcomputername').should == "WinNT://testcomputername" + Puppet::Util::Windows::ADSI.computer_uri('testcomputername').should == "WinNT://testcomputername" end - describe ".sid_for_account", :if => Puppet.features.microsoft_windows? do + describe ".sid_for_account" do it "should return nil if the account does not exist" do - Puppet::Util::Windows::Security.expects(:name_to_sid).with('foobar').returns nil + Puppet::Util::Windows::SID.expects(:name_to_sid).with('foobar').returns nil - Puppet::Util::ADSI.sid_for_account('foobar').should be_nil + Puppet::Util::Windows::ADSI.sid_for_account('foobar').should be_nil end it "should return a SID for a passed user or group name" do - Puppet::Util::Windows::Security.expects(:name_to_sid).with('testers').returns 'S-1-5-32-547' + Puppet::Util::Windows::SID.expects(:name_to_sid).with('testers').returns 'S-1-5-32-547' - Puppet::Util::ADSI.sid_for_account('testers').should == 'S-1-5-32-547' + Puppet::Util::Windows::ADSI.sid_for_account('testers').should == 'S-1-5-32-547' end it "should return a SID for a passed fully-qualified user or group name" do - Puppet::Util::Windows::Security.expects(:name_to_sid).with('MACHINE\testers').returns 'S-1-5-32-547' + Puppet::Util::Windows::SID.expects(:name_to_sid).with('MACHINE\testers').returns 'S-1-5-32-547' - Puppet::Util::ADSI.sid_for_account('MACHINE\testers').should == 'S-1-5-32-547' + Puppet::Util::Windows::ADSI.sid_for_account('MACHINE\testers').should == 'S-1-5-32-547' end end - describe ".sid_uri", :if => Puppet.features.microsoft_windows? do + describe ".computer_name" do + it "should return a non-empty ComputerName string" do + Puppet::Util::Windows::ADSI.instance_variable_set(:@computer_name, nil) + Puppet::Util::Windows::ADSI.computer_name.should_not be_empty + end + end + + describe ".sid_uri" do it "should raise an error when the input is not a SID object" do [Object.new, {}, 1, :symbol, '', nil].each do |input| expect { - Puppet::Util::ADSI.sid_uri(input) + Puppet::Util::Windows::ADSI.sid_uri(input) }.to raise_error(Puppet::Error, /Must use a valid SID object/) end end it "should return a SID uri for a well-known SID (SYSTEM)" do sid = Win32::Security::SID.new('SYSTEM') - Puppet::Util::ADSI.sid_uri(sid).should == 'WinNT://S-1-5-18' + Puppet::Util::Windows::ADSI.sid_uri(sid).should == 'WinNT://S-1-5-18' end end - describe Puppet::Util::ADSI::User do + describe Puppet::Util::Windows::ADSI::User do let(:username) { 'testuser' } let(:domain) { 'DOMAIN' } let(:domain_username) { "#{domain}\\#{username}"} it "should generate the correct URI" do - Puppet::Util::ADSI.stubs(:sid_uri_safe).returns(nil) - Puppet::Util::ADSI::User.uri(username).should == "WinNT://./#{username},user" + Puppet::Util::Windows::ADSI.stubs(:sid_uri_safe).returns(nil) + Puppet::Util::Windows::ADSI::User.uri(username).should == "WinNT://./#{username},user" end it "should generate the correct URI for a user with a domain" do - Puppet::Util::ADSI.stubs(:sid_uri_safe).returns(nil) - Puppet::Util::ADSI::User.uri(username, domain).should == "WinNT://#{domain}/#{username},user" + Puppet::Util::Windows::ADSI.stubs(:sid_uri_safe).returns(nil) + Puppet::Util::Windows::ADSI::User.uri(username, domain).should == "WinNT://#{domain}/#{username},user" end it "should be able to parse a username without a domain" do - Puppet::Util::ADSI::User.parse_name(username).should == [username, '.'] + Puppet::Util::Windows::ADSI::User.parse_name(username).should == [username, '.'] end it "should be able to parse a username with a domain" do - Puppet::Util::ADSI::User.parse_name(domain_username).should == [username, domain] + Puppet::Util::Windows::ADSI::User.parse_name(domain_username).should == [username, domain] end it "should raise an error with a username that contains a /" do expect { - Puppet::Util::ADSI::User.parse_name("#{domain}/#{username}") + Puppet::Util::Windows::ADSI::User.parse_name("#{domain}/#{username}") }.to raise_error(Puppet::Error, /Value must be in DOMAIN\\user style syntax/) end @@ -100,63 +107,61 @@ describe Puppet::Util::ADSI do adsi_user = stub('adsi') connection.expects(:Create).with('user', username).returns(adsi_user) - Puppet::Util::ADSI::Group.expects(:exists?).with(username).returns(false) + Puppet::Util::Windows::ADSI::Group.expects(:exists?).with(username).returns(false) - user = Puppet::Util::ADSI::User.create(username) + user = Puppet::Util::Windows::ADSI::User.create(username) - user.should be_a(Puppet::Util::ADSI::User) + user.should be_a(Puppet::Util::Windows::ADSI::User) user.native_user.should == adsi_user end it "should be able to check the existence of a user" do - Puppet::Util::ADSI.stubs(:sid_uri_safe).returns(nil) - Puppet::Util::ADSI.expects(:connect).with("WinNT://./#{username},user").returns connection - Puppet::Util::ADSI::User.exists?(username).should be_true + Puppet::Util::Windows::ADSI.stubs(:sid_uri_safe).returns(nil) + Puppet::Util::Windows::ADSI.expects(:connect).with("WinNT://./#{username},user").returns connection + Puppet::Util::Windows::ADSI::User.exists?(username).should be_true end it "should be able to check the existence of a domain user" do - Puppet::Util::ADSI.stubs(:sid_uri_safe).returns(nil) - Puppet::Util::ADSI.expects(:connect).with("WinNT://#{domain}/#{username},user").returns connection - Puppet::Util::ADSI::User.exists?(domain_username).should be_true + Puppet::Util::Windows::ADSI.stubs(:sid_uri_safe).returns(nil) + Puppet::Util::Windows::ADSI.expects(:connect).with("WinNT://#{domain}/#{username},user").returns connection + Puppet::Util::Windows::ADSI::User.exists?(domain_username).should be_true end - it "should be able to confirm the existence of a user with a well-known SID", - :if => Puppet.features.microsoft_windows? do + it "should be able to confirm the existence of a user with a well-known SID" do system_user = Win32::Security::SID::LocalSystem # ensure that the underlying OS is queried here - Puppet::Util::ADSI.unstub(:connect) - Puppet::Util::ADSI::User.exists?(system_user).should be_true + Puppet::Util::Windows::ADSI.unstub(:connect) + Puppet::Util::Windows::ADSI::User.exists?(system_user).should be_true end - it "should return nil with an unknown SID", - :if => Puppet.features.microsoft_windows? do + it "should return nil with an unknown SID" do bogus_sid = 'S-1-2-3-4' # ensure that the underlying OS is queried here - Puppet::Util::ADSI.unstub(:connect) - Puppet::Util::ADSI::User.exists?(bogus_sid).should be_false + Puppet::Util::Windows::ADSI.unstub(:connect) + Puppet::Util::Windows::ADSI::User.exists?(bogus_sid).should be_false end it "should be able to delete a user" do connection.expects(:Delete).with('user', username) - Puppet::Util::ADSI::User.delete(username) + Puppet::Util::Windows::ADSI::User.delete(username) end it "should return an enumeration of IADsUser wrapped objects" do - Puppet::Util::ADSI.stubs(:sid_uri_safe).returns(nil) + Puppet::Util::Windows::ADSI.stubs(:sid_uri_safe).returns(nil) name = 'Administrator' wmi_users = [stub('WMI', :name => name)] - Puppet::Util::ADSI.expects(:execquery).with('select name from win32_useraccount where localaccount = "TRUE"').returns(wmi_users) + Puppet::Util::Windows::ADSI.expects(:execquery).with('select name from win32_useraccount where localaccount = "TRUE"').returns(wmi_users) native_user = stub('IADsUser') homedir = "C:\\Users\\#{name}" native_user.expects(:Get).with('HomeDirectory').returns(homedir) - Puppet::Util::ADSI.expects(:connect).with("WinNT://./#{name},user").returns(native_user) + Puppet::Util::Windows::ADSI.expects(:connect).with("WinNT://./#{name},user").returns(native_user) - users = Puppet::Util::ADSI::User.to_a + users = Puppet::Util::Windows::ADSI::User.to_a users.length.should == 1 users[0].name.should == name users[0]['HomeDirectory'].should == homedir @@ -165,7 +170,7 @@ describe Puppet::Util::ADSI do describe "an instance" do let(:adsi_user) { stub('user', :objectSID => []) } let(:sid) { stub(:account => username, :domain => 'testcomputername') } - let(:user) { Puppet::Util::ADSI::User.new(username, adsi_user) } + let(:user) { Puppet::Util::Windows::ADSI::User.new(username, adsi_user) } it "should provide its groups as a list of names" do names = ["group1", "group2"] @@ -178,8 +183,8 @@ describe Puppet::Util::ADSI do end it "should be able to test whether a given password is correct" do - Puppet::Util::ADSI::User.expects(:logon).with(username, 'pwdwrong').returns(false) - Puppet::Util::ADSI::User.expects(:logon).with(username, 'pwdright').returns(true) + Puppet::Util::Windows::ADSI::User.expects(:logon).with(username, 'pwdwrong').returns(false) + Puppet::Util::Windows::ADSI::User.expects(:logon).with(username, 'pwdright').returns(true) user.password_is?('pwdwrong').should be_false user.password_is?('pwdright').should be_true @@ -198,16 +203,16 @@ describe Puppet::Util::ADSI do user.password = 'pwd' end - it "should generate the correct URI", :if => Puppet.features.microsoft_windows? do - Puppet::Util::Windows::Security.stubs(:octet_string_to_sid_object).returns(sid) + it "should generate the correct URI" do + Puppet::Util::Windows::SID.stubs(:octet_string_to_sid_object).returns(sid) user.uri.should == "WinNT://testcomputername/#{username},user" end - describe "when given a set of groups to which to add the user", :if => Puppet.features.microsoft_windows? do + describe "when given a set of groups to which to add the user" do let(:groups_to_set) { 'group1,group2' } before(:each) do - Puppet::Util::Windows::Security.stubs(:octet_string_to_sid_object).returns(sid) + Puppet::Util::Windows::SID.stubs(:octet_string_to_sid_object).returns(sid) user.expects(:groups).returns ['group2', 'group3'] end @@ -219,9 +224,9 @@ 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 + Puppet::Util::Windows::ADSI.expects(:sid_uri).with(sid).returns("WinNT://testcomputername/#{username},user").twice + Puppet::Util::Windows::ADSI.expects(:connect).with('WinNT://./group1,group').returns group1 + Puppet::Util::Windows::ADSI.expects(:connect).with('WinNT://./group3,group').returns group3 user.set_groups(groups_to_set, false) end @@ -232,8 +237,8 @@ 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 + Puppet::Util::Windows::ADSI.expects(:sid_uri).with(sid).returns("WinNT://testcomputername/#{username},user") + Puppet::Util::Windows::ADSI.expects(:connect).with('WinNT://./group1,group').returns group1 user.set_groups(groups_to_set, true) end @@ -242,50 +247,50 @@ describe Puppet::Util::ADSI do end end - describe Puppet::Util::ADSI::Group do + describe Puppet::Util::Windows::ADSI::Group do let(:groupname) { 'testgroup' } describe "an instance" do let(:adsi_group) { stub 'group' } - let(:group) { Puppet::Util::ADSI::Group.new(groupname, adsi_group) } + let(:group) { Puppet::Util::Windows::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') + it "should be able to add a member (deprecated)" do + Puppet.expects(:deprecation_warning).with('Puppet::Util::Windows::ADSI::Group#add_members is deprecated; please use Puppet::Util::Windows::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") + Puppet::Util::Windows::SID.expects(:name_to_sid_object).with('someone').returns(someone_sid) + Puppet::Util::Windows::ADSI.expects(:sid_uri).with(someone_sid).returns("WinNT://testcomputername/someone,user") adsi_group.expects(:Add).with("WinNT://testcomputername/someone,user") group.add_member('someone') end - it "should raise when adding a member that can't resolve to a SID (deprecated)", :if => Puppet.features.microsoft_windows? do + it "should raise when adding a member that can't resolve to a SID (deprecated)" 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') + it "should be able to remove a member (deprecated)" do + Puppet.expects(:deprecation_warning).with('Puppet::Util::Windows::ADSI::Group#remove_members is deprecated; please use Puppet::Util::Windows::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") + Puppet::Util::Windows::SID.expects(:name_to_sid_object).with('someone').returns(someone_sid) + Puppet::Util::Windows::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 + it "should raise when removing a member that can't resolve to a SID (deprecated)" 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') } + describe "should be able to use SID objects" do + let(:system) { Puppet::Util::Windows::SID.name_to_sid_object('SYSTEM') } it "to add a member" do adsi_group.expects(:Add).with("WinNT://S-1-5-18") @@ -310,7 +315,7 @@ describe Puppet::Util::ADSI do group.members.should =~ names end - it "should be able to add a list of users to a group", :if => Puppet.features.microsoft_windows? do + it "should be able to add a list of users to a group" do names = ['DOMAIN\user1', 'user2'] sids = [ stub(:account => 'user1', :domain => 'DOMAIN'), @@ -319,14 +324,14 @@ describe Puppet::Util::ADSI do ] # 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]) + Puppet::Util::Windows::SID.expects(:octet_string_to_sid_object).with([0]).returns(sids[0]) + Puppet::Util::Windows::SID.expects(:octet_string_to_sid_object).with([1]).returns(sids[1]) - Puppet::Util::Windows::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::Windows::SID.expects(:name_to_sid_object).with('user2').returns(sids[1]) + Puppet::Util::Windows::SID.expects(:name_to_sid_object).with('DOMAIN2\user3').returns(sids[2]) - Puppet::Util::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") + Puppet::Util::Windows::ADSI.expects(:sid_uri).with(sids[0]).returns("WinNT://DOMAIN/user1,user") + Puppet::Util::Windows::ADSI.expects(:sid_uri).with(sids[2]).returns("WinNT://DOMAIN2/user3,user") members = names.each_with_index.map{|n,i| stub(:Name => n, :objectSID => [i])} adsi_group.expects(:Members).returns members @@ -337,7 +342,7 @@ describe Puppet::Util::ADSI do group.set_members(['user2', 'DOMAIN2\user3']) end - it "should raise an error when a username does not resolve to a SID", :if => Puppet.features.microsoft_windows? do + it "should raise an error when a username does not resolve to a SID" do expect { adsi_group.expects(:Members).returns [] group.set_members(['foobar']) @@ -345,81 +350,79 @@ describe Puppet::Util::ADSI do end it "should generate the correct URI" do - Puppet::Util::ADSI.stubs(:sid_uri_safe).returns(nil) + Puppet::Util::Windows::ADSI.stubs(:sid_uri_safe).returns(nil) group.uri.should == "WinNT://./#{groupname},group" end end it "should generate the correct URI" do - Puppet::Util::ADSI.stubs(:sid_uri_safe).returns(nil) - Puppet::Util::ADSI::Group.uri("people").should == "WinNT://./people,group" + Puppet::Util::Windows::ADSI.stubs(:sid_uri_safe).returns(nil) + Puppet::Util::Windows::ADSI::Group.uri("people").should == "WinNT://./people,group" end it "should be able to create a group" do adsi_group = stub("adsi") connection.expects(:Create).with('group', groupname).returns(adsi_group) - Puppet::Util::ADSI::User.expects(:exists?).with(groupname).returns(false) + Puppet::Util::Windows::ADSI::User.expects(:exists?).with(groupname).returns(false) - group = Puppet::Util::ADSI::Group.create(groupname) + group = Puppet::Util::Windows::ADSI::Group.create(groupname) - group.should be_a(Puppet::Util::ADSI::Group) + group.should be_a(Puppet::Util::Windows::ADSI::Group) group.native_group.should == adsi_group end it "should be able to confirm the existence of a group" do - Puppet::Util::ADSI.stubs(:sid_uri_safe).returns(nil) - Puppet::Util::ADSI.expects(:connect).with("WinNT://./#{groupname},group").returns connection + Puppet::Util::Windows::ADSI.stubs(:sid_uri_safe).returns(nil) + Puppet::Util::Windows::ADSI.expects(:connect).with("WinNT://./#{groupname},group").returns connection - Puppet::Util::ADSI::Group.exists?(groupname).should be_true + Puppet::Util::Windows::ADSI::Group.exists?(groupname).should be_true end - it "should be able to confirm the existence of a group with a well-known SID", - :if => Puppet.features.microsoft_windows? do + it "should be able to confirm the existence of a group with a well-known SID" do service_group = Win32::Security::SID::Service # ensure that the underlying OS is queried here - Puppet::Util::ADSI.unstub(:connect) - Puppet::Util::ADSI::Group.exists?(service_group).should be_true + Puppet::Util::Windows::ADSI.unstub(:connect) + Puppet::Util::Windows::ADSI::Group.exists?(service_group).should be_true end - it "should return nil with an unknown SID", - :if => Puppet.features.microsoft_windows? do + it "should return nil with an unknown SID" do bogus_sid = 'S-1-2-3-4' # ensure that the underlying OS is queried here - Puppet::Util::ADSI.unstub(:connect) - Puppet::Util::ADSI::Group.exists?(bogus_sid).should be_false + Puppet::Util::Windows::ADSI.unstub(:connect) + Puppet::Util::Windows::ADSI::Group.exists?(bogus_sid).should be_false end it "should be able to delete a group" do connection.expects(:Delete).with('group', groupname) - Puppet::Util::ADSI::Group.delete(groupname) + Puppet::Util::Windows::ADSI::Group.delete(groupname) end it "should return an enumeration of IADsGroup wrapped objects" do - Puppet::Util::ADSI.stubs(:sid_uri_safe).returns(nil) + Puppet::Util::Windows::ADSI.stubs(:sid_uri_safe).returns(nil) name = 'Administrators' wmi_groups = [stub('WMI', :name => name)] - Puppet::Util::ADSI.expects(:execquery).with('select name from win32_group where localaccount = "TRUE"').returns(wmi_groups) + Puppet::Util::Windows::ADSI.expects(:execquery).with('select name from win32_group where localaccount = "TRUE"').returns(wmi_groups) native_group = stub('IADsGroup') native_group.expects(:Members).returns([stub(:Name => 'Administrator')]) - Puppet::Util::ADSI.expects(:connect).with("WinNT://./#{name},group").returns(native_group) + Puppet::Util::Windows::ADSI.expects(:connect).with("WinNT://./#{name},group").returns(native_group) - groups = Puppet::Util::ADSI::Group.to_a + groups = Puppet::Util::Windows::ADSI::Group.to_a groups.length.should == 1 groups[0].name.should == name groups[0].members.should == ['Administrator'] end end - describe Puppet::Util::ADSI::UserProfile do + describe Puppet::Util::Windows::ADSI::UserProfile do it "should be able to delete a user profile" do connection.expects(:Delete).with("Win32_UserProfile.SID='S-A-B-C'") - Puppet::Util::ADSI::UserProfile.delete('S-A-B-C') + Puppet::Util::Windows::ADSI::UserProfile.delete('S-A-B-C') end it "should warn on 2003" do @@ -431,7 +434,7 @@ describe Puppet::Util::ADSI do Exception occurred.") Puppet.expects(:warning).with("Cannot delete user profile for 'S-A-B-C' prior to Vista SP1") - Puppet::Util::ADSI::UserProfile.delete('S-A-B-C') + Puppet::Util::Windows::ADSI::UserProfile.delete('S-A-B-C') end end end diff --git a/spec/unit/util/windows/api_types_spec.rb b/spec/unit/util/windows/api_types_spec.rb new file mode 100644 index 000000000..a1e1c76c9 --- /dev/null +++ b/spec/unit/util/windows/api_types_spec.rb @@ -0,0 +1,28 @@ +# encoding: UTF-8 +#!/usr/bin/env ruby + +require 'spec_helper' + +describe "FFI::MemoryPointer", :if => Puppet.features.microsoft_windows? do + context "read_wide_string" do + let (:string) { "foo_bar" } + + it "should properly roundtrip a given string" do + read_string = nil + FFI::MemoryPointer.from_string_to_wide_string(string) do |ptr| + read_string = ptr.read_wide_string(string.length) + end + + read_string.should == string + end + + it "should return a given string in the default encoding" do + read_string = nil + FFI::MemoryPointer.from_string_to_wide_string(string) do |ptr| + read_string = ptr.read_wide_string(string.length) + end + + read_string.encoding.should == Encoding.default_external + end + end +end diff --git a/spec/unit/util/windows/registry_spec.rb b/spec/unit/util/windows/registry_spec.rb index 636ba0c93..ed52539a5 100755 --- a/spec/unit/util/windows/registry_spec.rb +++ b/spec/unit/util/windows/registry_spec.rb @@ -1,7 +1,6 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/windows' -require 'puppet/util/windows/registry' describe Puppet::Util::Windows::Registry, :if => Puppet::Util::Platform.windows? do subject do @@ -43,12 +42,14 @@ describe Puppet::Util::Windows::Registry, :if => Puppet::Util::Platform.windows? yielded.should == subkey end - [described_class::KEY64, described_class::KEY32].each do |access| - it "should open the key for read access 0x#{access.to_s(16)}" do - mode = described_class::KEY_READ | access - hkey.expects(:open).with(path, mode) + if Puppet::Util::Platform.windows? + [described_class::KEY64, described_class::KEY32].each do |access| + it "should open the key for read access 0x#{access.to_s(16)}" do + mode = described_class::KEY_READ | access + hkey.expects(:open).with(path, mode) - subject.open(name, path, mode) {|reg| } + subject.open(name, path, mode) {|reg| } + end end end diff --git a/spec/unit/util/windows/sid_spec.rb b/spec/unit/util/windows/sid_spec.rb index 770512188..2748f13c6 100755 --- a/spec/unit/util/windows/sid_spec.rb +++ b/spec/unit/util/windows/sid_spec.rb @@ -4,12 +4,9 @@ require 'spec_helper' describe "Puppet::Util::Windows::SID", :if => Puppet.features.microsoft_windows? do if Puppet.features.microsoft_windows? require 'puppet/util/windows' - class SIDTester - include Puppet::Util::Windows::SID - end end - let(:subject) { SIDTester.new } + let(:subject) { Puppet::Util::Windows::SID } let(:sid) { Win32::Security::SID::LocalSystem } let(:invalid_sid) { 'bogus' } let(:unknown_sid) { 'S-0-0-0' } @@ -50,7 +47,7 @@ describe "Puppet::Util::Windows::SID", :if => Puppet.features.microsoft_windows? 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./) + }.to raise_error(SystemCallError, /No mapping between account names and security IDs was done./) end end @@ -159,7 +156,7 @@ describe "Puppet::Util::Windows::SID", :if => Puppet.features.microsoft_windows? it "should raise if the conversion fails" do subject.expects(:string_to_sid_ptr).with(sid). - raises(Puppet::Util::Windows::Error.new("Failed to convert string SID: #{sid}", Windows::Error::ERROR_ACCESS_DENIED)) + raises(Puppet::Util::Windows::Error.new("Failed to convert string SID: #{sid}", Puppet::Util::Windows::Error::ERROR_ACCESS_DENIED)) expect { subject.string_to_sid_ptr(sid) {|ptr| } diff --git a/spec/unit/util/windows/string_spec.rb b/spec/unit/util/windows/string_spec.rb index 60f7e6449..5c6473e70 100644 --- a/spec/unit/util/windows/string_spec.rb +++ b/spec/unit/util/windows/string_spec.rb @@ -50,5 +50,9 @@ describe "Puppet::Util::Windows::String", :if => Puppet.features.microsoft_windo it "should convert an UTF-32BE string" do converts_to_wide_string("bob\u00E8".encode(Encoding::UTF_32BE)) end + + it "should return a nil when given a nil" do + wide_string(nil).should == nil + end end end diff --git a/spec/unit/util/zaml_spec.rb b/spec/unit/util/zaml_spec.rb index 56c5cb719..b239b4a84 100755 --- a/spec/unit/util/zaml_spec.rb +++ b/spec/unit/util/zaml_spec.rb @@ -69,7 +69,11 @@ describe "Pure ruby yaml implementation" do end it "serializes a time in UTC" do - pending("not supported on Windows", :if => Puppet.features.microsoft_windows? && RUBY_VERSION[0,3] == '1.8') do + bad_rubies = + RUBY_VERSION[0,3] == '1.8' || + RUBY_VERSION[0,3] == '2.0' && RUBY_PLATFORM == 'i386-mingw32' + + pending("not supported on Windows", :if => Puppet.features.microsoft_windows? && bad_rubies) do the_time_in("Europe/London").should be_equivalent_to(the_time_in_yaml_offset_by("+00:00")) end end |