summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLamont Granquist <lamont@scriptkiddie.org>2016-05-04 07:41:39 -0700
committerLamont Granquist <lamont@scriptkiddie.org>2016-05-04 07:41:39 -0700
commit9d047b8565c24bb5dfebeef24ecaa1680893fd32 (patch)
tree4dee7bb8a47c53fea2e7777ab8932b83012435d4
parent98ead2710216d29774c50a0d80fca050919a67a2 (diff)
parent910f22567f0a588418c4112d419e7bccbe4ca0cc (diff)
downloadchef-9d047b8565c24bb5dfebeef24ecaa1680893fd32.tar.gz
Merge pull request #4869 from chef/lcg/package-lazy-monads
Lazy'ing candidate_version in package provider
-rw-r--r--lib/chef/decorator.rb81
-rw-r--r--lib/chef/decorator/lazy.rb60
-rw-r--r--lib/chef/decorator/lazy_array.rb59
-rw-r--r--lib/chef/provider/package.rb62
-rw-r--r--lib/chef/provider/package/dpkg.rb4
-rw-r--r--lib/chef/provider/package/rubygems.rb20
-rw-r--r--spec/unit/decorator/lazy_array_spec.rb58
-rw-r--r--spec/unit/decorator/lazy_spec.rb39
-rw-r--r--spec/unit/decorator_spec.rb142
-rw-r--r--spec/unit/provider/package/rubygems_spec.rb42
-rw-r--r--spec/unit/provider/package_spec.rb13
11 files changed, 545 insertions, 35 deletions
diff --git a/lib/chef/decorator.rb b/lib/chef/decorator.rb
new file mode 100644
index 0000000000..546c49baed
--- /dev/null
+++ b/lib/chef/decorator.rb
@@ -0,0 +1,81 @@
+#--
+# Copyright:: Copyright 2016 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "delegate"
+
+class Chef
+ class Decorator < SimpleDelegator
+ NULL = ::Object.new
+
+ def initialize(obj = NULL)
+ @__defined_methods__ = []
+ super unless obj.equal?(NULL)
+ end
+
+ # if we wrap a nil then decorator.nil? should be true
+ def nil?
+ __getobj__.nil?
+ end
+
+ # if we wrap a Hash then decorator.is_a?(Hash) should be true
+ def is_a?(klass)
+ __getobj__.is_a?(klass) || super
+ end
+
+ # if we wrap a Hash then decorator.kind_of?(Hash) should be true
+ def kind_of?(klass)
+ __getobj__.kind_of?(klass) || super
+ end
+
+ # reset our methods on the instance if the object changes under us (this also
+ # clears out the closure over the target we create in method_missing below)
+ def __setobj__(obj)
+ __reset_methods__
+ super
+ end
+
+ # this is the ruby 2.2/2.3 implementation of Delegator#method_missing() with
+ # adding the define_singleton_method call and @__defined_methods__ tracking
+ def method_missing(m, *args, &block)
+ r = true
+ target = self.__getobj__ { r = false }
+
+ if r && target.respond_to?(m)
+ # these next 4 lines are the patched code
+ define_singleton_method(m) do |*args, &block|
+ target.__send__(m, *args, &block)
+ end
+ @__defined_methods__.push(m)
+ target.__send__(m, *args, &block)
+ elsif ::Kernel.respond_to?(m, true)
+ ::Kernel.instance_method(m).bind(self).call(*args, &block)
+ else
+ super(m, *args, &block)
+ end
+ end
+
+ private
+
+ # used by __setobj__ to clear the methods we've built on the instance.
+ def __reset_methods__
+ @__defined_methods__.each do |m|
+ singleton_class.send(:undef_method, m)
+ end
+ @__defined_methods__ = []
+ end
+ end
+end
diff --git a/lib/chef/decorator/lazy.rb b/lib/chef/decorator/lazy.rb
new file mode 100644
index 0000000000..71a2151d65
--- /dev/null
+++ b/lib/chef/decorator/lazy.rb
@@ -0,0 +1,60 @@
+#--
+# Copyright:: Copyright 2016 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "chef/decorator"
+
+class Chef
+ class Decorator
+ # Lazy wrapper to delay construction of an object until a method is
+ # called against the object.
+ #
+ # @example
+ #
+ # def foo
+ # puts "allocated"
+ # "value"
+ # end
+ #
+ # a = Chef::Decorator::Lazy.new { foo }
+ #
+ # puts "started"
+ # a
+ # puts "still lazy"
+ # puts a
+ #
+ # outputs:
+ #
+ # started
+ # still lazy
+ # allocated
+ # value
+ #
+ # @since 12.10.x
+ class Lazy < Decorator
+ def initialize(&block)
+ super
+ @block = block
+ end
+
+ def __getobj__
+ __setobj__(@block.call) unless defined?(@delegate_sd_obj)
+ super
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/decorator/lazy_array.rb b/lib/chef/decorator/lazy_array.rb
new file mode 100644
index 0000000000..dc8ea832ee
--- /dev/null
+++ b/lib/chef/decorator/lazy_array.rb
@@ -0,0 +1,59 @@
+#--
+# Copyright:: Copyright 2016 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "chef/decorator/lazy"
+
+class Chef
+ class Decorator
+ # Lazy Array around Lazy Objects
+ #
+ # This only lazys access through `#[]`. In order to implement #each we need to
+ # know how many items we have and what their indexes are, so we'd have to evalute
+ # the proc which makes that impossible. You can call methods like #each and the
+ # decorator will forward the method, but item access will not be lazy.
+ #
+ # #at() and #fetch() are not implemented but technically could be.
+ #
+ # @example
+ # def foo
+ # puts "allocated"
+ # "value"
+ # end
+ #
+ # a = Chef::Decorator::LazyArray.new { [ foo ] }
+ #
+ # puts "started"
+ # a[0]
+ # puts "still lazy"
+ # puts a[0]
+ #
+ # outputs:
+ #
+ # started
+ # still lazy
+ # allocated
+ # value
+ #
+ # @since 12.10.x
+ class LazyArray < Lazy
+ def [](idx)
+ block = @block
+ Lazy.new { block.call[idx] }
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/package.rb b/lib/chef/provider/package.rb
index ca9b526920..6d67cdbbb2 100644
--- a/lib/chef/provider/package.rb
+++ b/lib/chef/provider/package.rb
@@ -22,6 +22,7 @@ require "chef/mixin/subclass_directive"
require "chef/log"
require "chef/file_cache"
require "chef/platform"
+require "chef/decorator/lazy_array"
class Chef
class Provider
@@ -256,9 +257,41 @@ class Chef
options ? " #{options}" : ""
end
- # this is public and overridden by subclasses (rubygems package implements '>=' and '~>' operators)
- def target_version_already_installed?(current_version, new_version)
- new_version == current_version
+ # Check the current_version against either the candidate_version or the new_version
+ #
+ # For some reason the windows provider subclasses this (to implement passing Arrays to
+ # versions for some reason other than multipackage stuff, which is mildly terrifying).
+ #
+ # This MUST have 'equality' semantics -- the exact thing matches the exact thing.
+ #
+ # The current_version should probably be dropped out of the method signature, it should
+ # always be the first argument.
+ #
+ # The name is not just bad, but i find it completely misleading, consider:
+ #
+ # target_version_already_installed?(current_version, new_version)
+ # target_version_already_installed?(current_version, candidate_version)
+ #
+ # which of those is the 'target_version'? i'd say the new_version and i'm confused when
+ # i see it called with the candidate_version.
+ #
+ # `current_version_equals?(version)` would be a better name
+ def target_version_already_installed?(current_version, target_version)
+ return false unless current_version && target_version
+ current_version == target_version
+ end
+
+ # Check the current_version against the new_resource.version, possibly using fuzzy
+ # matching criteria.
+ #
+ # Subclasses MAY override this to provide fuzzy matching on the resource ('>=' and '~>' stuff)
+ #
+ # This should only ever be offered the same arguments (so they should most likely be
+ # removed from the method signature).
+ #
+ # `new_version_satisfied?()` might be a better name
+ def version_requirement_satisfied?(current_version, new_version)
+ target_version_already_installed?(current_version, new_version)
end
# @todo: extract apt/dpkg specific preseeding to a helper class
@@ -354,12 +387,15 @@ class Chef
each_package do |package_name, new_version, current_version, candidate_version|
case action
when :upgrade
-
- if !candidate_version
- Chef::Log.debug("#{new_resource} #{package_name} has no candidate_version to upgrade to")
+ if target_version_already_installed?(current_version, new_version)
+ # this is an odd use case
+ Chef::Log.debug("#{new_resource} #{package_name} #{new_version} is already installed -- you are equality pinning with an :upgrade action, this may be deprecated in the future")
target_version_array.push(nil)
- elsif current_version == candidate_version
- Chef::Log.debug("#{new_resource} #{package_name} the #{candidate_version} is already installed")
+ elsif target_version_already_installed?(current_version, candidate_version)
+ Chef::Log.debug("#{new_resource} #{package_name} #{candidate_version} is already installed")
+ target_version_array.push(nil)
+ elsif candidate_version.nil?
+ Chef::Log.debug("#{new_resource} #{package_name} has no candidate_version to upgrade to")
target_version_array.push(nil)
else
Chef::Log.debug("#{new_resource} #{package_name} is out of date, will upgrade to #{candidate_version}")
@@ -369,7 +405,7 @@ class Chef
when :install
if new_version
- if target_version_already_installed?(current_version, new_version)
+ if version_requirement_satisfied?(current_version, new_version)
Chef::Log.debug("#{new_resource} #{package_name} #{current_version} satisifies #{new_version} requirement")
target_version_array.push(nil)
else
@@ -411,7 +447,7 @@ class Chef
begin
missing = []
each_package do |package_name, new_version, current_version, candidate_version|
- missing.push(package_name) if candidate_version.nil? && current_version.nil?
+ missing.push(package_name) if current_version.nil? && candidate_version.nil?
end
missing
end
@@ -436,7 +472,7 @@ class Chef
missing = []
each_package do |package_name, new_version, current_version, candidate_version|
next if new_version.nil? || current_version.nil?
- if candidate_version.nil? && !target_version_already_installed?(current_version, new_version)
+ if !version_requirement_satisfied?(current_version, new_version) && candidate_version.nil?
missing.push(package_name)
end
end
@@ -468,7 +504,9 @@ class Chef
# @return [Array] candidate_version(s) as an array
def candidate_version_array
- [ candidate_version ].flatten
+ # NOTE: even with use_multipackage_api candidate_version may be a bare nil and need wrapping
+ # ( looking at you, dpkg provider... )
+ Chef::Decorator::LazyArray.new { [ candidate_version ].flatten }
end
# @return [Array] current_version(s) as an array
diff --git a/lib/chef/provider/package/dpkg.rb b/lib/chef/provider/package/dpkg.rb
index a5a80e14d6..e57f58e052 100644
--- a/lib/chef/provider/package/dpkg.rb
+++ b/lib/chef/provider/package/dpkg.rb
@@ -23,9 +23,9 @@ class Chef
class Provider
class Package
class Dpkg < Chef::Provider::Package
- DPKG_REMOVED = /^Status: deinstall ok config-files/
+ DPKG_REMOVED = /^Status: deinstall ok config-files/
DPKG_INSTALLED = /^Status: install ok installed/
- DPKG_VERSION = /^Version: (.+)$/
+ DPKG_VERSION = /^Version: (.+)$/
provides :dpkg_package, os: "linux"
diff --git a/lib/chef/provider/package/rubygems.rb b/lib/chef/provider/package/rubygems.rb
index a15ee1bdea..eb5a87099f 100644
--- a/lib/chef/provider/package/rubygems.rb
+++ b/lib/chef/provider/package/rubygems.rb
@@ -482,20 +482,16 @@ class Chef
def candidate_version
@candidate_version ||= begin
- if target_version_already_installed?(@current_resource.version, @new_resource.version)
- nil
- elsif source_is_remote?
- @gem_env.candidate_version_from_remote(gem_dependency, *gem_sources).to_s
- else
- @gem_env.candidate_version_from_file(gem_dependency, @new_resource.source).to_s
- end
- end
+ if source_is_remote?
+ @gem_env.candidate_version_from_remote(gem_dependency, *gem_sources).to_s
+ else
+ @gem_env.candidate_version_from_file(gem_dependency, @new_resource.source).to_s
+ end
+ end
end
- def target_version_already_installed?(current_version, new_version)
- return false unless current_version
- return false if new_version.nil?
-
+ def version_requirement_satisfied?(current_version, new_version)
+ return false unless current_version && new_version
Gem::Requirement.new(new_version).satisfied_by?(Gem::Version.new(current_version))
end
diff --git a/spec/unit/decorator/lazy_array_spec.rb b/spec/unit/decorator/lazy_array_spec.rb
new file mode 100644
index 0000000000..0c5c2eeee0
--- /dev/null
+++ b/spec/unit/decorator/lazy_array_spec.rb
@@ -0,0 +1,58 @@
+#
+# Author:: Lamont Granquist (<lamont@chef.io>)
+# Copyright:: Copyright 2015-2016, Chef Software Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "spec_helper"
+
+describe Chef::Decorator::LazyArray do
+ def foo
+ @foo ||= 1
+ end
+
+ def bar
+ @bar ||= 2
+ end
+
+ let(:decorator) do
+ Chef::Decorator::LazyArray.new { [ foo, bar ] }
+ end
+
+ it "behaves like an array" do
+ expect(decorator[0]).to eql(1)
+ expect(decorator[1]).to eql(2)
+ end
+
+ it "accessing the array elements is lazy" do
+ expect(decorator[0].class).to eql(Chef::Decorator::Lazy)
+ expect(decorator[1].class).to eql(Chef::Decorator::Lazy)
+ expect(@foo).to be nil
+ expect(@bar).to be nil
+ end
+
+ it "calling a method on the array element runs the proc (and both elements are autovivified)" do
+ expect(decorator[0].nil?).to be false
+ expect(@foo).to equal(1)
+ expect(@bar).to equal(2)
+ end
+
+ it "if we loop over the elements and do nothing then its not lazy" do
+ # we don't know how many elements there are unless we evaluate the proc
+ decorator.each { |i| }
+ expect(@foo).to equal(1)
+ expect(@bar).to equal(2)
+ end
+end
diff --git a/spec/unit/decorator/lazy_spec.rb b/spec/unit/decorator/lazy_spec.rb
new file mode 100644
index 0000000000..4ea8301b63
--- /dev/null
+++ b/spec/unit/decorator/lazy_spec.rb
@@ -0,0 +1,39 @@
+#
+# Author:: Lamont Granquist (<lamont@chef.io>)
+# Copyright:: Copyright 2015-2016, Chef Software Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "spec_helper"
+
+describe Chef::Decorator::Lazy do
+ let(:decorator) do
+ @a = 0
+ Chef::Decorator::Lazy.new { @a = @a + 1 }
+ end
+
+ it "decorates an object" do
+ expect(decorator.even?).to be false
+ end
+
+ it "the proc runs and does work" do
+ expect(decorator).to eql(1)
+ end
+
+ it "creating the decorator does not cause the proc to run" do
+ decorator
+ expect(@a).to eql(0)
+ end
+end
diff --git a/spec/unit/decorator_spec.rb b/spec/unit/decorator_spec.rb
new file mode 100644
index 0000000000..6d73db2cc4
--- /dev/null
+++ b/spec/unit/decorator_spec.rb
@@ -0,0 +1,142 @@
+#
+# Author:: Lamont Granquist (<lamont@chef.io>)
+# Copyright:: Copyright 2015-2016, Chef Software Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "spec_helper"
+
+def impersonates_a(klass)
+ it "#is_a?(#{klass}) is true" do
+ expect(decorator.is_a?(klass)).to be true
+ end
+
+ it "#is_a?(Chef::Decorator) is true" do
+ expect(decorator.is_a?(Chef::Decorator)).to be true
+ end
+
+ it "#kind_of?(#{klass}) is true" do
+ expect(decorator.kind_of?(klass)).to be true
+ end
+
+ it "#kind_of?(Chef::Decorator) is true" do
+ expect(decorator.kind_of?(Chef::Decorator)).to be true
+ end
+
+ it "#instance_of?(#{klass}) is false" do
+ expect(decorator.instance_of?(klass)).to be false
+ end
+
+ it "#instance_of?(Chef::Decorator) is true" do
+ expect(decorator.instance_of?(Chef::Decorator)).to be true
+ end
+
+ it "#class is Chef::Decorator" do
+ expect(decorator.class).to eql(Chef::Decorator)
+ end
+end
+
+describe Chef::Decorator do
+ let(:obj) {}
+ let(:decorator) { Chef::Decorator.new(obj) }
+
+ context "when the obj is a string" do
+ let(:obj) { "STRING" }
+
+ impersonates_a(String)
+
+ it "#nil? is false" do
+ expect(decorator.nil?).to be false
+ end
+
+ it "!! is true" do
+ expect(!!decorator).to be true
+ end
+
+ it "dup returns a decorator" do
+ expect(decorator.dup.class).to be Chef::Decorator
+ end
+
+ it "dup dup's the underlying thing" do
+ expect(decorator.dup.__getobj__).not_to equal(decorator.__getobj__)
+ end
+ end
+
+ context "when the obj is a nil" do
+ let(:obj) { nil }
+
+ it "#nil? is true" do
+ expect(decorator.nil?).to be true
+ end
+
+ it "!! is false" do
+ expect(!!decorator).to be false
+ end
+
+ impersonates_a(NilClass)
+ end
+
+ context "when the obj is an empty Hash" do
+ let(:obj) { {} }
+
+ impersonates_a(Hash)
+
+ it "formats it correctly through ffi-yajl and not the JSON gem" do
+ # this relies on a quirk of pretty formatting whitespace between yajl and ruby's JSON
+ expect(FFI_Yajl::Encoder.encode(decorator, pretty: true)).to eql("{\n\n}\n")
+ end
+ end
+
+ context "whent he obj is a Hash with elements" do
+ let(:obj) { { foo: "bar", baz: "qux" } }
+
+ impersonates_a(Hash)
+
+ it "dup is shallow on the Hash" do
+ expect(decorator.dup[:baz]).to equal(decorator[:baz])
+ end
+
+ it "deep mutating the dup'd hash mutates the origin" do
+ decorator.dup[:baz] << "qux"
+ expect(decorator[:baz]).to eql("quxqux")
+ end
+ end
+
+ context "memoizing methods" do
+ let(:obj) { {} }
+
+ it "calls method_missing only once" do
+ expect(decorator).to receive(:method_missing).once.and_call_original
+ expect(decorator.keys).to eql([])
+ expect(decorator.keys).to eql([])
+ end
+
+ it "switching a Hash to an Array responds to keys then does not" do
+ expect(decorator.respond_to?(:keys)).to be true
+ expect(decorator.keys).to eql([])
+ decorator.__setobj__([])
+ expect(decorator.respond_to?(:keys)).to be false
+ expect { decorator.keys }.to raise_error(NoMethodError)
+ end
+
+ it "memoization of methods happens on the instances, not the classes" do
+ decorator2 = Chef::Decorator.new([])
+ expect(decorator.respond_to?(:keys)).to be true
+ expect(decorator.keys).to eql([])
+ expect(decorator2.respond_to?(:keys)).to be false
+ expect { decorator2.keys }.to raise_error(NoMethodError)
+ end
+ end
+end
diff --git a/spec/unit/provider/package/rubygems_spec.rb b/spec/unit/provider/package/rubygems_spec.rb
index 6e77e7c112..f87c261ec0 100644
--- a/spec/unit/provider/package/rubygems_spec.rb
+++ b/spec/unit/provider/package/rubygems_spec.rb
@@ -496,12 +496,36 @@ describe Chef::Provider::Package::Rubygems do
provider.load_current_resource
end
- it "does not query for available versions when the current version is the target version" do
- expect(provider.candidate_version).to be_nil
+ context "when the current version is the target version" do
+ it "does not query for available versions" do
+ # NOTE: odd use case -- we've equality pinned a version, but are calling :upgrade
+ expect(provider.gem_env).not_to receive(:candidate_version_from_remote)
+ expect(provider.gem_env).not_to receive(:install)
+ provider.run_action(:upgrade)
+ expect(new_resource).not_to be_updated_by_last_action
+ end
end
- context "when the current version is not the target version" do
- let(:target_version) { "9000.0.2" }
+ context "when the current version satisfies the target version requirement" do
+ let(:target_version) { ">= 0" }
+
+ it "does not query for available versions on install" do
+ expect(provider.gem_env).not_to receive(:candidate_version_from_remote)
+ expect(provider.gem_env).not_to receive(:install)
+ provider.run_action(:install)
+ expect(new_resource).not_to be_updated_by_last_action
+ end
+
+ it "queries for available versions on upgrade" do
+ expect(provider.gem_env).to receive(:candidate_version_from_remote).
+ and_return(Gem::Version.new("9000.0.2"))
+ expect(provider.gem_env).to receive(:install)
+ provider.run_action(:upgrade)
+ expect(new_resource).to be_updated_by_last_action
+ end
+ end
+
+ context "when the requested source is a remote server" do
let(:source) { "http://mygems.example.com" }
it "determines the candidate version by querying the remote gem servers" do
@@ -669,6 +693,11 @@ describe Chef::Provider::Package::Rubygems do
expect(provider.gem_env).not_to receive(:install)
provider.run_action(:install)
end
+
+ it "performs the upgrade" do
+ expect(provider.gem_env).to receive(:install)
+ provider.run_action(:upgrade)
+ end
end
context "if the fuzzy operator is used" do
@@ -679,6 +708,11 @@ describe Chef::Provider::Package::Rubygems do
expect(provider.gem_env).not_to receive(:install)
provider.run_action(:install)
end
+
+ it "it upgrades an existing gem" do
+ expect(provider.gem_env).to receive(:install)
+ provider.run_action(:upgrade)
+ end
end
end
end
diff --git a/spec/unit/provider/package_spec.rb b/spec/unit/provider/package_spec.rb
index abf0322868..e4354d0db4 100644
--- a/spec/unit/provider/package_spec.rb
+++ b/spec/unit/provider/package_spec.rb
@@ -566,8 +566,11 @@ describe "Chef::Provider::Package - Multi" do
let(:new_resource) { Chef::Resource::Package.new(%w{emacs vi}) }
let(:current_resource) { Chef::Resource::Package.new(%w{emacs vi}) }
let(:candidate_version) { [ "1.0", "6.2" ] }
+ class MyPackageProvider < Chef::Provider::Package
+ use_multipackage_api
+ end
let(:provider) do
- provider = Chef::Provider::Package.new(new_resource, run_context)
+ provider = MyPackageProvider.new(new_resource, run_context)
provider.current_resource = current_resource
provider.candidate_version = candidate_version
provider
@@ -731,7 +734,7 @@ describe "Chef::Provider::Package - Multi" do
it "should remove the packages if all are installed" do
expect(provider).to be_removing_package
- expect(provider).to receive(:remove_package).with(%w{emacs vi}, nil)
+ expect(provider).to receive(:remove_package).with(%w{emacs vi}, [nil])
provider.run_action(:remove)
expect(new_resource).to be_updated
expect(new_resource).to be_updated_by_last_action
@@ -740,7 +743,7 @@ describe "Chef::Provider::Package - Multi" do
it "should remove the packages if some are installed" do
current_resource.version ["1.0", nil]
expect(provider).to be_removing_package
- expect(provider).to receive(:remove_package).with(%w{emacs vi}, nil)
+ expect(provider).to receive(:remove_package).with(%w{emacs vi}, [nil])
provider.run_action(:remove)
expect(new_resource).to be_updated
expect(new_resource).to be_updated_by_last_action
@@ -787,7 +790,7 @@ describe "Chef::Provider::Package - Multi" do
it "should purge the packages if all are installed" do
expect(provider).to be_removing_package
- expect(provider).to receive(:purge_package).with(%w{emacs vi}, nil)
+ expect(provider).to receive(:purge_package).with(%w{emacs vi}, [nil])
provider.run_action(:purge)
expect(new_resource).to be_updated
expect(new_resource).to be_updated_by_last_action
@@ -796,7 +799,7 @@ describe "Chef::Provider::Package - Multi" do
it "should purge the packages if some are installed" do
current_resource.version ["1.0", nil]
expect(provider).to be_removing_package
- expect(provider).to receive(:purge_package).with(%w{emacs vi}, nil)
+ expect(provider).to receive(:purge_package).with(%w{emacs vi}, [nil])
provider.run_action(:purge)
expect(new_resource).to be_updated
expect(new_resource).to be_updated_by_last_action