spec/unit/provider/package/dpkg_spec.rb in puppet-3.2.4 vs spec/unit/provider/package/dpkg_spec.rb in puppet-3.3.0.rc2
- old
+ new
@@ -1,227 +1,402 @@
#! /usr/bin/env ruby
require 'spec_helper'
require 'stringio'
-provider = Puppet::Type.type(:package).provider(:dpkg)
+provider_class = Puppet::Type.type(:package).provider(:dpkg)
-describe provider do
- before do
- @resource = stub 'resource', :[] => "asdf"
- @provider = provider.new(@resource)
- @provider.expects(:execute).never # forbid "manual" executions
+describe provider_class do
+ let(:bash_version) { '4.2-5ubuntu3' }
+ let(:bash_installed_output) do <<-EOS
+install ok installed bash #{bash_version} :DESC: GNU Bourne Again SHell
+ Bash is an sh-compatible command language interpreter that executes
+ commands read from the standard input or from a file. Bash also
+ incorporates useful features from the Korn and C shells (ksh and csh).
+ .
+ Bash is ultimately intended to be a conformant implementation of the
+ IEEE POSIX Shell and Tools specification (IEEE Working Group 1003.2).
+ .
+ The Programmable Completion Code, by Ian Macdonald, is now found in
+ the bash-completion package.
+:DESC:
+ EOS
+ end
+ let(:bash_installed_io) { StringIO.new(bash_installed_output) }
- @fakeresult = "install ok installed asdf 1.0\n"
+ let(:vim_installed_output) do <<-EOS
+install ok installed vim 2:7.3.547-6ubuntu5 :DESC: Vi IMproved - enhanced vi editor
+ Vim is an almost compatible version of the UNIX editor Vi.
+ .
+ Many new features have been added: multi level undo, syntax
+ highlighting, command line history, on-line help, filename
+ completion, block operations, folding, Unicode support, etc.
+ .
+ This package contains a version of vim compiled with a rather
+ standard set of features. This package does not provide a GUI
+ version of Vim. See the other vim-* packages if you need more
+ (or less).
+:DESC:
+ EOS
end
+ let(:all_installed_io) { StringIO.new([bash_installed_output, vim_installed_output].join) }
+ let(:args) { ['myquery', '-W', '--showformat', %Q{'${Status} ${Package} ${Version} :DESC: ${Description}\\n:DESC:\\n'}] }
+ let(:resource_name) { 'package' }
+ let(:resource) { stub 'resource', :[] => resource_name }
+ let(:provider) { provider_class.new(resource) }
+
+ before do
+ provider_class.stubs(:command).with(:dpkgquery).returns 'myquery'
+ end
+
it "should have documentation" do
- provider.doc.should be_instance_of(String)
+ provider_class.doc.should be_instance_of(String)
end
describe "when listing all instances" do
- before do
- provider.stubs(:command).with(:dpkgquery).returns "myquery"
- end
it "should use dpkg-query" do
- provider.expects(:command).with(:dpkgquery).returns "myquery"
- Puppet::Util::Execution.expects(:execpipe).with("myquery -W --showformat '${Status} ${Package} ${Version}\\n'").yields StringIO.new(@fakeresult)
+ Puppet::Util::Execution.expects(:execpipe).with(args).yields bash_installed_io
- provider.instances
+ provider_class.instances
end
- it "should create and return an instance with each parsed line from dpkg-query" do
- pipe = mock 'pipe'
- pipe.expects(:each).never
- pipe.expects(:each_line).yields @fakeresult
- Puppet::Util::Execution.expects(:execpipe).yields pipe
+ it "should create and return an instance for a single dpkg-query entry" do
+ Puppet::Util::Execution.expects(:execpipe).with(args).yields bash_installed_io
- asdf = mock 'pkg1'
- provider.expects(:new).with(:ensure => "1.0", :error => "ok", :desired => "install", :name => "asdf", :status => "installed", :provider => :dpkg).returns asdf
+ installed = mock 'bash'
+ provider_class.expects(:new).with(:ensure => "4.2-5ubuntu3", :error => "ok", :desired => "install", :name => "bash", :status => "installed", :description => "GNU Bourne Again SHell", :provider => :dpkg).returns installed
- provider.instances.should == [asdf]
+ provider_class.instances.should == [installed]
end
+ it "should parse multiple dpkg-query multi-line entries in the output" do
+ Puppet::Util::Execution.expects(:execpipe).with(args).yields all_installed_io
+
+ bash = mock 'bash'
+ provider_class.expects(:new).with(:ensure => "4.2-5ubuntu3", :error => "ok", :desired => "install", :name => "bash", :status => "installed", :description => "GNU Bourne Again SHell", :provider => :dpkg).returns bash
+ vim = mock 'vim'
+ provider_class.expects(:new).with(:ensure => "2:7.3.547-6ubuntu5", :error => "ok", :desired => "install", :name => "vim", :status => "installed", :description => "Vi IMproved - enhanced vi editor", :provider => :dpkg).returns vim
+
+ provider_class.instances.should == [bash, vim]
+ end
+
it "should warn on and ignore any lines it does not understand" do
- pipe = mock 'pipe'
- pipe.expects(:each).never
- pipe.expects(:each_line).yields "foobar"
- Puppet::Util::Execution.expects(:execpipe).yields pipe
+ Puppet::Util::Execution.expects(:execpipe).with(args).yields StringIO.new('foobar')
Puppet.expects(:warning)
- provider.expects(:new).never
+ provider_class.expects(:new).never
- provider.instances.should == []
+ provider_class.instances.should == []
end
+
+ it "should not warn on extra multiline description lines which we are ignoring" do
+ Puppet::Util::Execution.expects(:execpipe).with(args).yields all_installed_io
+
+ Puppet.expects(:warning).never
+ provider_class.instances
+ end
+
+ it "should warn if encounters bad lines between good entries without failing" do
+ Puppet::Util::Execution.expects(:execpipe).with(args).yields StringIO.new([bash_installed_output, "foobar\n", vim_installed_output].join)
+
+ Puppet.expects(:warning)
+
+ bash = mock 'bash'
+ vim = mock 'vim'
+ provider_class.expects(:new).twice.returns(bash, vim)
+
+ provider_class.instances.should == [bash, vim]
+ end
+
+ it "should warn on a broken entry while still parsing a good one" do
+ Puppet::Util::Execution.expects(:execpipe).with(args).yields StringIO.new([
+ bash_installed_output,
+ %Q{install ok installed broken 1.0 this shouldn't be here :DESC: broken description\n extra description\n:DESC:\n},
+ vim_installed_output,
+ ].join)
+
+ Puppet.expects(:warning).times(3)
+
+ bash = mock('bash')
+ vim = mock('vim')
+ saved = mock('saved')
+ provider_class.expects(:new).twice.returns(bash, vim)
+
+ provider_class.instances.should == [bash, vim]
+ end
end
describe "when querying the current state" do
- it "should use dpkg-query" do
- @provider.expects(:dpkgquery).with("-W", "--showformat",'${Status} ${Package} ${Version}\\n', "asdf").returns @fakeresult
+ let(:query_args) { args.push(resource_name) }
- @provider.query
+ before do
+ provider.expects(:execute).never # forbid "manual" executions
end
+ # @return [StringIO] of bash dpkg-query output with :search string replaced
+ # by :replace string.
+ def replace_in_bash_output(search, replace)
+ StringIO.new(bash_installed_output.gsub(search, replace))
+ end
+
+ it "should use exec-pipe" do
+ Puppet::Util::Execution.expects(:execpipe).with(query_args).yields bash_installed_io
+
+ provider.query
+ end
+
it "should consider the package purged if dpkg-query fails" do
- @provider.expects(:dpkgquery).raises Puppet::ExecutionFailure.new("eh")
+ Puppet::Util::Execution.expects(:execpipe).with(query_args).raises Puppet::ExecutionFailure.new("eh")
- @provider.query[:ensure].should == :purged
+ provider.query[:ensure].should == :purged
end
- it "should return a hash of the found status with the desired state, error state, status, name, and 'ensure'" do
- @provider.expects(:dpkgquery).returns @fakeresult
+ it "should return a hash of the found package status for an installed package" do
+ Puppet::Util::Execution.expects(:execpipe).with(query_args).yields bash_installed_io
- @provider.query.should == {:ensure => "1.0", :error => "ok", :desired => "install", :name => "asdf", :status => "installed", :provider => :dpkg}
+ provider.query.should == {:ensure => "4.2-5ubuntu3", :error => "ok", :desired => "install", :name => "bash", :status => "installed", :provider => :dpkg, :description => "GNU Bourne Again SHell"}
end
it "should consider the package absent if the dpkg-query result cannot be interpreted" do
- @provider.expects(:dpkgquery).returns "somebaddata"
+ Puppet::Util::Execution.expects(:execpipe).with(query_args).yields StringIO.new("somebaddata")
- @provider.query[:ensure].should == :absent
+ provider.query[:ensure].should == :absent
end
it "should fail if an error is discovered" do
- @provider.expects(:dpkgquery).returns @fakeresult.sub("ok", "error")
+ Puppet::Util::Execution.expects(:execpipe).with(query_args).yields replace_in_bash_output("ok", "error")
- lambda { @provider.query }.should raise_error(Puppet::Error)
+ lambda { provider.query }.should raise_error(Puppet::Error)
end
it "should consider the package purged if it is marked 'not-installed'" do
- @provider.expects(:dpkgquery).returns @fakeresult.sub("installed", "not-installed")
+ not_installed_bash = bash_installed_output.gsub("installed", "not-installed")
+ not_installed_bash.gsub!(bash_version, "")
+ Puppet::Util::Execution.expects(:execpipe).with(query_args).yields StringIO.new(not_installed_bash)
- @provider.query[:ensure].should == :purged
+ provider.query[:ensure].should == :purged
end
it "should consider the package absent if it is marked 'config-files'" do
- @provider.expects(:dpkgquery).returns @fakeresult.sub("installed", "config-files")
- @provider.query[:ensure].should == :absent
+ Puppet::Util::Execution.expects(:execpipe).with(query_args).yields replace_in_bash_output("installed", "config-files")
+ provider.query[:ensure].should == :absent
end
it "should consider the package absent if it is marked 'half-installed'" do
- @provider.expects(:dpkgquery).returns @fakeresult.sub("installed", "half-installed")
- @provider.query[:ensure].should == :absent
+ Puppet::Util::Execution.expects(:execpipe).with(query_args).yields replace_in_bash_output("installed", "half-installed")
+ provider.query[:ensure].should == :absent
end
it "should consider the package absent if it is marked 'unpacked'" do
- @provider.expects(:dpkgquery).returns @fakeresult.sub("installed", "unpacked")
- @provider.query[:ensure].should == :absent
+ Puppet::Util::Execution.expects(:execpipe).with(query_args).yields replace_in_bash_output("installed", "unpacked")
+ provider.query[:ensure].should == :absent
end
it "should consider the package absent if it is marked 'half-configured'" do
- @provider.expects(:dpkgquery).returns @fakeresult.sub("installed", "half-configured")
- @provider.query[:ensure].should == :absent
+ Puppet::Util::Execution.expects(:execpipe).with(query_args).yields replace_in_bash_output("installed", "half-configured")
+ provider.query[:ensure].should == :absent
end
it "should consider the package held if its state is 'hold'" do
- @provider.expects(:dpkgquery).returns @fakeresult.sub("install", "hold")
- @provider.query[:ensure].should == :held
+ Puppet::Util::Execution.expects(:execpipe).with(query_args).yields replace_in_bash_output("install", "hold")
+ provider.query[:ensure].should == :held
end
end
+ describe "parsing tests" do
+ let(:resource_name) { 'name' }
+ let(:package_hash) do
+ {
+ :desired => 'desired',
+ :error => 'ok',
+ :status => 'status',
+ :name => resource_name,
+ :ensure => 'ensure',
+ :description => 'summary text',
+ :provider => :dpkg,
+ }
+ end
+ let(:query_args) { args.push(resource_name) }
+
+ it "warns about excess lines if encounters a delimiter in description but does not fail" do
+ broken_description = <<-EOS
+desired ok status name ensure :DESC: summary text
+ more description
+:DESC:
+ 1 whoops ^^ should not happen, because dpkg-query is supposed to prefix description lines with
+ 2 whitespace. So we should see three warnings for these four additional lines when we try
+ 3 and process next-pkg (vv the :DESC: is line number 4)
+:DESC:
+desired ok status next-pkg ensure :DESC: next summary
+:DESC:
+ EOS
+ Puppet.expects(:warning).times(4)
+
+ pipe = StringIO.new(broken_description)
+ provider_class.parse_multi_line(pipe).should == package_hash
+
+ next_package = package_hash.merge(:name => 'next-pkg', :description => 'next summary')
+
+ hash = provider_class.parse_multi_line(pipe) until hash # warn about bad lines
+ hash.should == next_package
+ end
+
+ def parser_test(dpkg_output_string, gold_hash)
+ pipe = StringIO.new(dpkg_output_string)
+ Puppet::Util::Execution.expects(:execpipe).with(query_args).yields pipe
+ Puppet.expects(:warning).never
+
+ provider.query.should == gold_hash
+ end
+
+ it "should parse properly even if delimiter is in version" do
+ version_delimiter = <<-EOS
+desired ok status name 1.2.3-:DESC: :DESC: summary text
+ more description
+:DESC:
+ EOS
+ parser_test(version_delimiter, package_hash.merge(:ensure => '1.2.3-:DESC:'))
+ end
+
+ it "should parse properly even if delimiter is name" do
+ name_delimiter = <<-EOS
+desired ok status :DESC: ensure :DESC: summary text
+ more description
+:DESC:
+ EOS
+ parser_test(name_delimiter, package_hash.merge(:name => ':DESC:'))
+ end
+
+ it "should parse properly even if optional ensure field is missing" do
+ no_ensure = <<-EOS
+desired ok status name :DESC: summary text
+ more description and note^ two spaces surround the hole where 'ensure' field would be...
+:DESC:
+ EOS
+ parser_test(no_ensure, package_hash.merge(:ensure => ''))
+ end
+
+ it "should parse properly even if extra delimiter is in summary" do
+ extra_description_delimiter = <<-EOS
+desired ok status name ensure :DESC: summary text
+ :DESC: should be completely ignored because of leading space which dpkg-query should ensure
+:DESC:
+ EOS
+ parser_test(extra_description_delimiter, package_hash)
+ end
+
+ it "should parse properly even if package description is completely missing" do
+ no_description = "desired ok status name ensure :DESC: \n:DESC:"
+ parser_test(no_description, package_hash.merge(:description => ''))
+ end
+ end
+
it "should be able to install" do
- @provider.should respond_to(:install)
+ provider.should respond_to(:install)
end
describe "when installing" do
before do
- @resource.stubs(:[]).with(:source).returns "mypkg"
+ resource.stubs(:[]).with(:source).returns "mypkg"
end
it "should fail to install if no source is specified in the resource" do
- @resource.expects(:[]).with(:source).returns nil
+ resource.expects(:[]).with(:source).returns nil
- lambda { @provider.install }.should raise_error(ArgumentError)
+ lambda { provider.install }.should raise_error(ArgumentError)
end
it "should use 'dpkg -i' to install the package" do
- @resource.expects(:[]).with(:source).returns "mypackagefile"
- @provider.expects(:unhold)
- @provider.expects(:dpkg).with { |*command| command[-1] == "mypackagefile" and command[-2] == "-i" }
+ resource.expects(:[]).with(:source).returns "mypackagefile"
+ provider.expects(:unhold)
+ provider.expects(:dpkg).with { |*command| command[-1] == "mypackagefile" and command[-2] == "-i" }
- @provider.install
+ provider.install
end
it "should keep old config files if told to do so" do
- @resource.expects(:[]).with(:configfiles).returns :keep
- @provider.expects(:unhold)
- @provider.expects(:dpkg).with { |*command| command[0] == "--force-confold" }
+ resource.expects(:[]).with(:configfiles).returns :keep
+ provider.expects(:unhold)
+ provider.expects(:dpkg).with { |*command| command[0] == "--force-confold" }
- @provider.install
+ provider.install
end
it "should replace old config files if told to do so" do
- @resource.expects(:[]).with(:configfiles).returns :replace
- @provider.expects(:unhold)
- @provider.expects(:dpkg).with { |*command| command[0] == "--force-confnew" }
+ resource.expects(:[]).with(:configfiles).returns :replace
+ provider.expects(:unhold)
+ provider.expects(:dpkg).with { |*command| command[0] == "--force-confnew" }
- @provider.install
+ provider.install
end
it "should ensure any hold is removed" do
- @provider.expects(:unhold).once
- @provider.expects(:dpkg)
- @provider.install
+ provider.expects(:unhold).once
+ provider.expects(:dpkg)
+ provider.install
end
end
describe "when holding or unholding" do
+ let(:tempfile) { stub 'tempfile', :print => nil, :close => nil, :flush => nil, :path => "/other/file" }
+
before do
- @tempfile = stub 'tempfile', :print => nil, :close => nil, :flush => nil, :path => "/other/file"
- @tempfile.stubs(:write)
- Tempfile.stubs(:new).returns @tempfile
+ tempfile.stubs(:write)
+ Tempfile.stubs(:new).returns tempfile
end
it "should install first if holding" do
- @provider.stubs(:execute)
- @provider.expects(:install).once
- @provider.hold
+ provider.stubs(:execute)
+ provider.expects(:install).once
+ provider.hold
end
it "should execute dpkg --set-selections when holding" do
- @provider.stubs(:install)
- @provider.expects(:execute).with([:dpkg, '--set-selections'], {:failonfail => false, :combine => false, :stdinfile => @tempfile.path}).once
- @provider.hold
+ provider.stubs(:install)
+ provider.expects(:execute).with([:dpkg, '--set-selections'], {:failonfail => false, :combine => false, :stdinfile => tempfile.path}).once
+ provider.hold
end
it "should execute dpkg --set-selections when unholding" do
- @provider.stubs(:install)
- @provider.expects(:execute).with([:dpkg, '--set-selections'], {:failonfail => false, :combine => false, :stdinfile => @tempfile.path}).once
- @provider.hold
+ provider.stubs(:install)
+ provider.expects(:execute).with([:dpkg, '--set-selections'], {:failonfail => false, :combine => false, :stdinfile => tempfile.path}).once
+ provider.hold
end
end
it "should use :install to update" do
- @provider.expects(:install)
- @provider.update
+ provider.expects(:install)
+ provider.update
end
describe "when determining latest available version" do
it "should return the version found by dpkg-deb" do
- @resource.expects(:[]).with(:source).returns "myfile"
- @provider.expects(:dpkg_deb).with { |*command| command[-1] == "myfile" }.returns "asdf\t1.0"
- @provider.latest.should == "1.0"
+ resource.expects(:[]).with(:source).returns "myfile"
+ provider.expects(:dpkg_deb).with { |*command| command[-1] == "myfile" }.returns "package\t1.0"
+ provider.latest.should == "1.0"
end
it "should warn if the package file contains a different package" do
- @provider.expects(:dpkg_deb).returns("foo\tversion")
- @provider.expects(:warning)
- @provider.latest
+ provider.expects(:dpkg_deb).returns("foo\tversion")
+ provider.expects(:warning)
+ provider.latest
end
it "should cope with names containing ++" do
- @resource = stub 'resource', :[] => "asdf++"
- @provider = provider.new(@resource)
- @provider.expects(:dpkg_deb).returns "asdf++\t1.0"
- @provider.latest.should == "1.0"
+ resource = stub 'resource', :[] => "package++"
+ provider = provider_class.new(resource)
+ provider.expects(:dpkg_deb).returns "package++\t1.0"
+ provider.latest.should == "1.0"
end
end
it "should use 'dpkg -r' to uninstall" do
- @provider.expects(:dpkg).with("-r", "asdf")
- @provider.uninstall
+ provider.expects(:dpkg).with("-r", resource_name)
+ provider.uninstall
end
it "should use 'dpkg --purge' to purge" do
- @provider.expects(:dpkg).with("--purge", "asdf")
- @provider.purge
+ provider.expects(:dpkg).with("--purge", resource_name)
+ provider.purge
end
end