diff options
author | Marc A. Paradise <marc.paradise@gmail.com> | 2021-03-23 15:05:34 -0400 |
---|---|---|
committer | Marc A. Paradise <marc.paradise@gmail.com> | 2021-03-23 15:14:10 -0400 |
commit | b87b6ee1d991fb71e167b8c75183257002622a82 (patch) | |
tree | bdb1f493e6c31873163cd922d5dc92bdb8d1188c | |
parent | cff97d80bee0d1269eed28f4c128f56e69da4aad (diff) | |
download | chef-mp/broken-gems.tar.gz |
This is a collection of hacked-up and pared-down testsmp/broken-gems
and debug logging that reproduces a corrupt state in
the Gem class. See comment in spec/spec_helper.rb labeled
'Hack warning' for all the gory details.
In master, this is temporarily worked around by enabling the
Gem.clear_paths in config.before(:each); that is commented out in this
branch so that it can be repro'd immediately.
Signed-off-by: Marc A. Paradise <marc.paradise@gmail.com>
-rw-r--r-- | chef-utils/lib/chef-utils/dsl/default_paths.rb | 8 | ||||
-rw-r--r-- | lib/chef/knife/supermarket_share.rb | 55 | ||||
-rw-r--r-- | lib/chef/provider/package/rubygems.rb | 16 | ||||
-rw-r--r-- | spec/spec_helper.rb | 28 | ||||
-rw-r--r-- | spec/unit/cookbook/gem_installer_spec.rb | 53 | ||||
-rw-r--r-- | spec/unit/knife/supermarket_share_spec.rb | 207 | ||||
-rw-r--r-- | spec/unit/mixin/shell_out_spec.rb | 277 | ||||
-rw-r--r-- | spec/unit/provider/package/rubygems_spec.rb | 1156 | ||||
-rw-r--r-- | tasks/rspec.rb | 14 |
9 files changed, 133 insertions, 1681 deletions
diff --git a/chef-utils/lib/chef-utils/dsl/default_paths.rb b/chef-utils/lib/chef-utils/dsl/default_paths.rb index b2eb359273..2f25a6f099 100644 --- a/chef-utils/lib/chef-utils/dsl/default_paths.rb +++ b/chef-utils/lib/chef-utils/dsl/default_paths.rb @@ -32,8 +32,8 @@ module ChefUtils # ensure the Ruby and Gem bindirs are included for omnibus chef installs new_paths = env_path.split(path_separator) [ __ruby_bindir, __gem_bindir ].compact.each do |path| - new_paths = [ path ] + new_paths unless new_paths.include?(path) - end + new_paths = [ path ] + new_paths unless new_paths.include?(path) + end __default_paths.each do |path| new_paths << path unless new_paths.include?(path) end @@ -51,7 +51,9 @@ module ChefUtils end def __gem_bindir - Gem.bindir + dir = Gem.bindir + puts "__gem_bindir = #{dir} " + dir end extend self diff --git a/lib/chef/knife/supermarket_share.rb b/lib/chef/knife/supermarket_share.rb index 49b3474566..56b9f8ff69 100644 --- a/lib/chef/knife/supermarket_share.rb +++ b/lib/chef/knife/supermarket_share.rb @@ -54,58 +54,7 @@ class Chef default: "https://supermarket.chef.io" def run - config[:cookbook_path] ||= Chef::Config[:cookbook_path] - - if @name_args.length < 1 - show_usage - ui.fatal("You must specify the cookbook name.") - exit(1) - elsif @name_args.length < 2 - cookbook_name = @name_args[0] - category = get_category(cookbook_name) - else - cookbook_name = @name_args[0] - category = @name_args[1] - end - - cl = Chef::CookbookLoader.new(config[:cookbook_path]) - if cl.cookbook_exists?(cookbook_name) - cookbook = cl[cookbook_name] - Chef::CookbookUploader.new(cookbook).validate_cookbooks - tmp_cookbook_dir = Chef::CookbookSiteStreamingUploader.create_build_dir(cookbook) - begin - Chef::Log.trace("Temp cookbook directory is #{tmp_cookbook_dir.inspect}") - ui.info("Making tarball #{cookbook_name}.tgz") - shell_out!("#{tar_cmd} -czf #{cookbook_name}.tgz #{cookbook_name}", cwd: tmp_cookbook_dir) - rescue => e - ui.error("Error making tarball #{cookbook_name}.tgz: #{e.message}. Increase log verbosity (-VV) for more information.") - Chef::Log.trace("\n#{e.backtrace.join("\n")}") - exit(1) - end - - if config[:dry_run] - ui.info("Not uploading #{cookbook_name}.tgz due to --dry-run flag.") - result = shell_out!("#{tar_cmd} -tzf #{cookbook_name}.tgz", cwd: tmp_cookbook_dir) - ui.info(result.stdout) - FileUtils.rm_rf tmp_cookbook_dir - return - end - - begin - do_upload("#{tmp_cookbook_dir}/#{cookbook_name}.tgz", category, Chef::Config[:node_name], Chef::Config[:client_key]) - ui.info("Upload complete") - Chef::Log.trace("Removing local staging directory at #{tmp_cookbook_dir}") - FileUtils.rm_rf tmp_cookbook_dir - rescue => e - ui.error("Error uploading cookbook #{cookbook_name} to Supermarket: #{e.message}. Increase log verbosity (-VV) for more information.") - Chef::Log.trace("\n#{e.backtrace.join("\n")}") - exit(1) - end - - else - ui.error("Could not find cookbook #{cookbook_name} in your cookbook path.") - exit(1) - end + # shell_out("ls") end def get_category(cookbook_name) @@ -156,7 +105,9 @@ class Chef if shell_out("which gnutar").exitstatus.equal?(0) @tar_cmd = "gnutar" end + puts "TAR_CMD: #{@tar_cmd}" rescue Errno::ENOENT + puts "OOOPS - ENOENT!" end end @tar_cmd diff --git a/lib/chef/provider/package/rubygems.rb b/lib/chef/provider/package/rubygems.rb index e427cc0d24..4a6ae53fee 100644 --- a/lib/chef/provider/package/rubygems.rb +++ b/lib/chef/provider/package/rubygems.rb @@ -98,6 +98,20 @@ class Chef # installed versions, you get the one from Chef's Ruby's default # gems. This workaround ignores default gems entirely so we see # only the installed gems. + puts "IN EXECUTION" + # OKAY IN HERE, @paths is already defined in Gem! + puts "Gem.bindir #{Gem.bindir}" + puts "gem_specification.dirs: #{gem_specification.dirs}" + #require 'pry'; binding.pry + puts "Gem.path: #{Gem.path}" + puts "PathSupportGem.home: #{Gem::PathSupport.new(ENV).home}" + # puts "ENV['GEM_PATH'] which seems returned from gem_specification.dirs in PathSupport: #{ENV['GEM_PATH']}" + # puts "Gem::VERSION: #{Gem::VERSION}" + # ::File.open("/home/marc/projects/chef/env.out", "w") do |f| + # ENV.each do |k,v| + # f.write("#{k}=#{v}\n") + # end + # end stubs = gem_specification.send(:installed_stubs, gem_specification.dirs, "#{gem_dep.name}-*.gemspec") # Filter down to only to only stubs we actually want. The name # filter is needed in case of things like `foo-*.gemspec` also @@ -306,6 +320,7 @@ class Chef end def gem_paths + puts "OHHHH NOOOO!" if self.class.gempath_cache.key?(@gem_binary_location) self.class.gempath_cache[@gem_binary_location] else @@ -348,6 +363,7 @@ class Chef if self.class.platform_cache.key?(@gem_binary_location) self.class.platform_cache[@gem_binary_location] else + puts "OOPS I SHLLED OUT!" gem_environment = shell_out!("#{@gem_binary_location} env").stdout self.class.platform_cache[@gem_binary_location] = if jruby = gem_environment[JRUBY_PLATFORM] ["ruby", Gem::Platform.new(jruby)] diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 83cec749a7..64d9cdde39 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -232,6 +232,34 @@ RSpec.configure do |config| Chef.reset! + # Hack warning: + # + # Something across gem_installer_spec and mixlib_cli specs are polluting gem state + # so that the 'unmockening' test in rubygems_spec fails. Calling Gem.clear_paths this before each test + # (or anything that causes `Gem.paths` to initialize or reinitialize) prevents + # the problem; this is a hacky workaround. The better answer would be to understand + # what is happening in those first two specs that are causing gem state to get broken, + # and the nature of the breakage. Time doesn't allow for that yet, so I leave this note instead. + # + # This was discovered in the process of removing `knife` specs from `chef`; a fourth + # spec (supermarket_share_spec) was hiding the problem, because in one of its test + # it made a call Gem.binpath (indirectly) - which caused it to initialize sooner, and prevents the + # failure. + # + # Of note, if we put this Gem.clear_paths IN the failing test, it does fix it. But it + # would leave the trap set for when another gemspec-searching test is added in the wrong place. + # Interestingly, using other methods like Gem.paths to fix it won't work inside a test, because + # Gem paths are alrady initalized and the damage is done. + # entirely outside of the spec file. + + # + # The WIP to track this down (with a ton of puts debugs and the test cases pared down to nearly a minimum needed for a repro) + # is in mp/broken-gems if anyone wants to take that further. For now, calling Gem.clear_paths forces Gem to return + # to a sane internal state and avoids the issue. In that branch `rake gemfail` will run the specific tests in the order needed for + + # Uncomment this to 'fix' the error you get when running `rake mini`. + #Gem.clear_paths + Chef::ChefFS::FileSystemCache.instance.reset! Chef::Config.reset diff --git a/spec/unit/cookbook/gem_installer_spec.rb b/spec/unit/cookbook/gem_installer_spec.rb index 58843ac826..d31cc05b66 100644 --- a/spec/unit/cookbook/gem_installer_spec.rb +++ b/spec/unit/cookbook/gem_installer_spec.rb @@ -52,46 +52,15 @@ describe Chef::Cookbook::GemInstaller do b.instance_eval(gemfile.string) b end - before(:each) do - # Prepare mocks: using a StringIO instead of a File - expect(Dir).to receive(:mktmpdir).and_yield("") - expect(File).to receive(:open).and_yield(gemfile) - expect(gemfile).to receive(:path).and_return("") - expect(IO).to receive(:read).and_return("") - - end - - it "generates a valid Gemfile" do - expect(gem_installer).to receive(:shell_out!).and_return(shell_out) - expect { gem_installer.install }.to_not raise_error - - expect { bundler_dsl }.to_not raise_error - end - - it "generate a Gemfile with all constraints" do - expect(gem_installer).to receive(:shell_out!).and_return(shell_out) - expect { gem_installer.install }.to_not raise_error - - expect(bundler_dsl.dependencies.find { |d| d.name == "httpclient" }.requirements_list.length).to eql(2) - end + # # Prepare mocks: using a StringIO instead of a File + # expect(Dir).to receive(:mktmpdir).and_yield("") + # expect(File).to receive(:open).and_yield(gemfile) + # expect(gemfile).to receive(:path).and_return("") + # expect(IO).to receive(:read).and_return("") - it "generates a valid Gemfile when Chef::Config[:rubygems_url] is set to a String" do - expect(gem_installer).to receive(:shell_out!).and_return(shell_out) - Chef::Config[:rubygems_url] = "https://rubygems.org" - expect { gem_installer.install }.to_not raise_error - - expect(bundler_dsl.dependencies.find { |d| d.name == "httpclient" }.requirements_list.length).to eql(2) end - it "generates a valid Gemfile when Chef::Config[:rubygems_url] is set to an Array" do - expect(gem_installer).to receive(:shell_out!).and_return(shell_out) - Chef::Config[:rubygems_url] = [ "https://rubygems.org" ] - - expect { gem_installer.install }.to_not raise_error - - expect(bundler_dsl.dependencies.find { |d| d.name == "httpclient" }.requirements_list.length).to eql(2) - end it "skip metadata installation when Chef::Config[:skip_gem_metadata_installation] is set to true" do Chef::Config[:skip_gem_metadata_installation] = true @@ -99,16 +68,4 @@ describe Chef::Cookbook::GemInstaller do expect(gem_installer.install).to be_nil end - it "install metadata when Chef::Config[:skip_gem_metadata_installation] is not true" do - expect(gem_installer).to receive(:shell_out!).and_return(shell_out) - expect(Chef::Log).to receive(:info).and_return("") - expect(gem_installer.install).to be_nil - end - - it "install from local cache when Chef::Config[:gem_installer_bundler_options] is set to local" do - Chef::Config[:gem_installer_bundler_options] = "--local" - expect(gem_installer).to receive(:shell_out!).with(["bundle", "install", "--local"], any_args).and_return(shell_out) - expect(Chef::Log).to receive(:info).and_return("") - expect(gem_installer.install).to be_nil - end end diff --git a/spec/unit/knife/supermarket_share_spec.rb b/spec/unit/knife/supermarket_share_spec.rb index 088cef9dfd..0c6d56180a 100644 --- a/spec/unit/knife/supermarket_share_spec.rb +++ b/spec/unit/knife/supermarket_share_spec.rb @@ -1,20 +1,3 @@ -# -# Author:: Stephen Delano (<stephen@chef.io>) -# Copyright:: Copyright (c) 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" require "chef/knife/supermarket_share" @@ -23,186 +6,22 @@ require "chef/cookbook_site_streaming_uploader" describe Chef::Knife::SupermarketShare do - before(:each) do - @knife = Chef::Knife::SupermarketShare.new - # Merge default settings in. - @knife.merge_configs - @knife.name_args = %w{cookbook_name AwesomeSausage} + it "should pass now" do + # ShellOut was a red herring. Internally it eventually calls Gem.binpath, + # which is what was hiding the failure in the unmockening, below. + # class SOT + # include Chef::Mixin::ShellOut + # end - @cookbook = Chef::CookbookVersion.new("cookbook_name") + # class SOT + # include Chef::Mixin::ShellOut + # end + # SOT.new.shell_out("ls") - @cookbook_loader = double("Chef::CookbookLoader") - allow(@cookbook_loader).to receive(:cookbook_exists?).and_return(true) - allow(@cookbook_loader).to receive(:[]).and_return(@cookbook) - allow(Chef::CookbookLoader).to receive(:new).and_return(@cookbook_loader) - @noauth_rest = double(Chef::ServerAPI) - allow(@knife).to receive(:noauth_rest).and_return(@noauth_rest) - @cookbook_uploader = Chef::CookbookUploader.new("herpderp", rest: "norest") - allow(Chef::CookbookUploader).to receive(:new).and_return(@cookbook_uploader) - allow(@cookbook_uploader).to receive(:validate_cookbooks).and_return(true) - allow(Chef::CookbookSiteStreamingUploader).to receive(:create_build_dir).and_return(Dir.mktmpdir) - - allow(@knife).to receive(:shell_out!).and_return(true) - @stdout = StringIO.new - @stderr = StringIO.new - allow(@knife.ui).to receive(:stdout).and_return(@stdout) - allow(@knife.ui).to receive(:stderr).and_return(@stderr) + # This was coming through knife.run, I've moved it here because it's the minimum + # change to suppress the error. Uncomment it to see it in action. + # Gem.dir end - - describe "run" do - - before(:each) do - allow(@knife).to receive(:do_upload).and_return(true) - @category_response = { - "name" => "cookbook_name", - "category" => "Testing Category", - } - @bad_category_response = { - "error_code" => "NOT_FOUND", - "error_messages" => [ - "Resource does not exist.", - ], - } - end - - it "should set true to config[:dry_run] as default" do - expect(@knife.config[:dry_run]).to be_falsey - end - - it "should should print usage and exit when given no arguments" do - @knife.name_args = [] - expect(@knife).to receive(:show_usage) - expect(@knife.ui).to receive(:fatal) - expect { @knife.run }.to raise_error(SystemExit) - end - - it "should not fail when given only 1 argument and can determine category" do - @knife.name_args = ["cookbook_name"] - expect(@noauth_rest).to receive(:get).with("https://supermarket.chef.io/api/v1/cookbooks/cookbook_name").and_return(@category_response) - expect(@knife).to receive(:do_upload) - @knife.run - end - - it "should use a default category when given only 1 argument and cannot determine category" do - @knife.name_args = ["cookbook_name"] - expect(@noauth_rest).to receive(:get).with("https://supermarket.chef.io/api/v1/cookbooks/cookbook_name") { raise Net::HTTPClientException.new("404 Not Found", OpenStruct.new(code: "404")) } - expect(@knife).to receive(:do_upload) - expect { @knife.run }.to_not raise_error - end - - it "should print error and exit when given only 1 argument and Chef::ServerAPI throws an exception" do - @knife.name_args = ["cookbook_name"] - expect(@noauth_rest).to receive(:get).with("https://supermarket.chef.io/api/v1/cookbooks/cookbook_name") { raise Errno::ECONNREFUSED, "Connection refused" } - expect(@knife.ui).to receive(:fatal) - expect { @knife.run }.to raise_error(SystemExit) - end - - it "should check if the cookbook exists" do - expect(@cookbook_loader).to receive(:cookbook_exists?) - @knife.run - end - - it "should exit and log to error if the cookbook doesn't exist" do - allow(@cookbook_loader).to receive(:cookbook_exists?).and_return(false) - expect(@knife.ui).to receive(:error) - expect { @knife.run }.to raise_error(SystemExit) - end - - if File.exist?("/usr/bin/gnutar") || File.exist?("/bin/gnutar") - it "should use gnutar to make a tarball of the cookbook" do - expect(@knife).to receive(:shell_out!) do |args| - expect(args.to_s).to match(/gnutar -czf/) - end - @knife.run - end - else - it "should make a tarball of the cookbook" do - expect(@knife).to receive(:shell_out!) do |args| - expect(args.to_s).to match(/tar -czf/) - end - @knife.run - end - end - - it "should exit and log to error when the tarball creation fails" do - allow(@knife).to receive(:shell_out!).and_raise(Chef::Exceptions::Exec) - expect(@knife.ui).to receive(:error) - expect { @knife.run }.to raise_error(SystemExit) - end - - it "should upload the cookbook and clean up the tarball" do - expect(@knife).to receive(:do_upload) - expect(FileUtils).to receive(:rm_rf) - @knife.run - end - - context "when the --dry-run flag is specified" do - before do - allow(Chef::CookbookSiteStreamingUploader).to receive(:create_build_dir).and_return("/var/tmp/dummy") - @knife.config = { dry_run: true } - @so = instance_double("Mixlib::ShellOut") - allow(@knife).to receive(:shell_out!).and_return(@so) - allow(@so).to receive(:stdout).and_return("file") - end - - it "should list files in the tarball" do - allow(@knife).to receive(:tar_cmd).and_return("footar") - expect(@knife).to receive(:shell_out!).with("footar -czf #{@cookbook.name}.tgz #{@cookbook.name}", { cwd: "/var/tmp/dummy" }) - expect(@knife).to receive(:shell_out!).with("footar -tzf #{@cookbook.name}.tgz", { cwd: "/var/tmp/dummy" }) - @knife.run - end - - it "does not upload the cookbook" do - expect(@knife).not_to receive(:do_upload) - @knife.run - end - end - end - - describe "do_upload" do - - before(:each) do - @upload_response = double("Net::HTTPResponse") - allow(Chef::CookbookSiteStreamingUploader).to receive(:post).and_return(@upload_response) - - allow(File).to receive(:open).and_return(true) - end - - it 'should post the cookbook to "https://supermarket.chef.io"' do - response_text = Chef::JSONCompat.to_json({ uri: "https://supermarket.chef.io/cookbooks/cookbook_name" }) - allow(@upload_response).to receive(:body).and_return(response_text) - allow(@upload_response).to receive(:code).and_return(201) - expect(Chef::CookbookSiteStreamingUploader).to receive(:post).with(/supermarket\.chef\.io/, anything, anything, anything) - @knife.run - end - - it "should alert the user when a version already exists" do - response_text = Chef::JSONCompat.to_json({ error_messages: ["Version already exists"] }) - allow(@upload_response).to receive(:body).and_return(response_text) - allow(@upload_response).to receive(:code).and_return(409) - expect { @knife.run }.to raise_error(SystemExit) - expect(@stderr.string).to match(/ERROR(.+)cookbook already exists/) - end - - it "should pass any errors on to the user" do - response_text = Chef::JSONCompat.to_json({ error_messages: ["You're holding it wrong"] }) - allow(@upload_response).to receive(:body).and_return(response_text) - allow(@upload_response).to receive(:code).and_return(403) - expect { @knife.run }.to raise_error(SystemExit) - expect(@stderr.string).to match("ERROR(.*)You're holding it wrong") - end - - it "should print the body if no errors are exposed on failure" do - response_text = Chef::JSONCompat.to_json({ system_error: "Your call was dropped", reason: "There's a map for that" }) - allow(@upload_response).to receive(:body).and_return(response_text) - allow(@upload_response).to receive(:code).and_return(500) - expect(@knife.ui).to receive(:error).with(/#{Regexp.escape(response_text)}/) # .ordered - expect(@knife.ui).to receive(:error).with(/Unknown error/) # .ordered - expect { @knife.run }.to raise_error(SystemExit) - end - - end - end diff --git a/spec/unit/mixin/shell_out_spec.rb b/spec/unit/mixin/shell_out_spec.rb index 2b76a10e6c..d4b052a595 100644 --- a/spec/unit/mixin/shell_out_spec.rb +++ b/spec/unit/mixin/shell_out_spec.rb @@ -36,272 +36,47 @@ describe Chef::Mixin::ShellOut do end context "when testing individual methods" do - before(:each) do - @original_env = ENV.to_hash - ENV.clear - end - - after(:each) do - ENV.clear - ENV.update(@original_env) - end + before(:each) do + @original_env = ENV.to_hash + ENV.clear + end + # + after(:each) do + ENV.clear + ENV.update(@original_env) + end let(:retobj) { instance_double(Mixlib::ShellOut, "error!" => false) } let(:cmd) { "echo '#{rand(1000)}'" } - %i{shell_out shell_out!}.each do |method| + %i{shell_out}.each do |method| describe "##{method}" do describe "when the last argument is a Hash" do + # Fails for either one of these - ANYTHING that invokes? describe "and environment is an option" do it "should not change environment language settings when they are set to nil" do - options = { environment: { "LC_ALL" => nil, "LANGUAGE" => nil, "LANG" => nil, env_path => nil } } - expect(shell_out_obj).to receive(:__shell_out_command).with(cmd, **options).and_return(retobj) - shell_out_obj.send(method, cmd, **options) - end - - it "should not change environment language settings when they are set to non-nil" do - options = { environment: { "LC_ALL" => "en_US.UTF-8", "LANGUAGE" => "en_US.UTF-8", "LANG" => "en_US.UTF-8", env_path => "foo:bar:baz" } } - expect(shell_out_obj).to receive(:__shell_out_command).with(cmd, **options).and_return(retobj) - shell_out_obj.send(method, cmd, **options) - end - - it "should set environment language settings to the configured internal locale when they are not present" do - options = { environment: { "HOME" => "/Users/morty" } } - expect(shell_out_obj).to receive(:__shell_out_command).with(cmd, - environment: { - "HOME" => "/Users/morty", - "LC_ALL" => Chef::Config[:internal_locale], - "LANG" => Chef::Config[:internal_locale], - "LANGUAGE" => Chef::Config[:internal_locale], - env_path => shell_out_obj.default_paths, - }).and_return(retobj) - shell_out_obj.send(method, cmd, **options) - end - - it "should not mutate the options hash when it adds language settings" do - options = { environment: { "HOME" => "/Users/morty" } } - expect(shell_out_obj).to receive(:__shell_out_command).with(cmd, - environment: { - "HOME" => "/Users/morty", - "LC_ALL" => Chef::Config[:internal_locale], - "LANG" => Chef::Config[:internal_locale], - "LANGUAGE" => Chef::Config[:internal_locale], - env_path => shell_out_obj.default_paths, - }).and_return(retobj) - shell_out_obj.send(method, cmd, **options) - expect(options[:environment].key?("LC_ALL")).to be false - end - end + shell_out_obj.send(method, cmd ) - describe "and env is an option" do - it "should not change env when langauge options are set to nil" do - options = { env: { "LC_ALL" => nil, "LANG" => nil, "LANGUAGE" => nil, env_path => nil } } - expect(shell_out_obj).to receive(:__shell_out_command).with(cmd, **options).and_return(retobj) - shell_out_obj.send(method, cmd, **options) - end - - it "should not change env when language options are set to non-nil" do - options = { env: { "LC_ALL" => "de_DE.UTF-8", "LANG" => "de_DE.UTF-8", "LANGUAGE" => "de_DE.UTF-8", env_path => "foo:bar:baz" } } - expect(shell_out_obj).to receive(:__shell_out_command).with(cmd, **options).and_return(retobj) - shell_out_obj.send(method, cmd, **options) - end - - it "should set environment language settings to the configured internal locale when they are not present" do - options = { env: { "HOME" => "/Users/morty" } } - expect(shell_out_obj).to receive(:__shell_out_command).with(cmd, - env: { - "HOME" => "/Users/morty", - "LC_ALL" => Chef::Config[:internal_locale], - "LANG" => Chef::Config[:internal_locale], - "LANGUAGE" => Chef::Config[:internal_locale], - env_path => shell_out_obj.default_paths, - }).and_return(retobj) - shell_out_obj.send(method, cmd, **options) - end - - it "should not mutate the options hash when it adds language settings" do - options = { env: { "HOME" => "/Users/morty" } } - expect(shell_out_obj).to receive(:__shell_out_command).with(cmd, - env: { - "HOME" => "/Users/morty", - "LC_ALL" => Chef::Config[:internal_locale], - "LANG" => Chef::Config[:internal_locale], - "LANGUAGE" => Chef::Config[:internal_locale], - env_path => shell_out_obj.default_paths, - }).and_return(retobj) - shell_out_obj.send(method, cmd, **options) - expect(options[:env].key?("LC_ALL")).to be false end end - describe "and no env/environment option is present" do - it "should set environment language settings to the configured internal locale" do - options = { user: "morty" } - expect(shell_out_obj).to receive(:__shell_out_command).with(cmd, - user: "morty", - environment: { - "LC_ALL" => Chef::Config[:internal_locale], - "LANG" => Chef::Config[:internal_locale], - "LANGUAGE" => Chef::Config[:internal_locale], - env_path => shell_out_obj.default_paths, - }).and_return(retobj) - shell_out_obj.send(method, cmd, **options) - end - end + # describe "and no env/environment option is present" do + # it "should set environment language settings to the configured internal locale" do + # options = { user: "morty" } + # expect(shell_out_obj).to receive(:__shell_out_command).with(cmd, + # user: "morty", + # environment: { + # "LC_ALL" => Chef::Config[:internal_locale], + # "LANG" => Chef::Config[:internal_locale], + # "LANGUAGE" => Chef::Config[:internal_locale], + # env_path => shell_out_obj.default_paths, + # }).and_return(retobj) + # shell_out_obj.send(method, cmd, **options) + # end + # end end - describe "when the last argument is not a Hash" do - it "should set environment language settings to the configured internal locale" do - expect(shell_out_obj).to receive(:__shell_out_command).with(cmd, - environment: { - "LC_ALL" => Chef::Config[:internal_locale], - "LANG" => Chef::Config[:internal_locale], - "LANGUAGE" => Chef::Config[:internal_locale], - env_path => shell_out_obj.default_paths, - }).and_return(retobj) - shell_out_obj.send(method, cmd) - end - end - end - end - - describe "#shell_out default_env: false" do - - describe "when the last argument is a Hash" do - describe "and environment is an option" do - it "should not change environment['LC_ALL'] when set to nil" do - options = { environment: { "LC_ALL" => nil } } - expect(shell_out_obj).to receive(:__shell_out_command).with(cmd, options).and_return(true) - shell_out_obj.shell_out(cmd, **options, default_env: false) - end - - it "should not change environment['LC_ALL'] when set to non-nil" do - options = { environment: { "LC_ALL" => "en_US.UTF-8" } } - expect(shell_out_obj).to receive(:__shell_out_command).with(cmd, options).and_return(true) - shell_out_obj.shell_out(cmd, **options, default_env: false) - end - - it "should no longer set environment['LC_ALL'] to nil when 'LC_ALL' not present" do - options = { environment: { "HOME" => "/Users/morty" } } - expect(shell_out_obj).to receive(:__shell_out_command).with(cmd, options).and_return(true) - shell_out_obj.shell_out(cmd, **options, default_env: false) - end - end - - describe "and env is an option" do - it "should not change env when set to nil" do - options = { env: { "LC_ALL" => nil } } - expect(shell_out_obj).to receive(:__shell_out_command).with(cmd, options).and_return(true) - shell_out_obj.shell_out(cmd, **options, default_env: false) - end - - it "should not change env when set to non-nil" do - options = { env: { "LC_ALL" => "en_US.UTF-8" } } - expect(shell_out_obj).to receive(:__shell_out_command).with(cmd, options).and_return(true) - shell_out_obj.shell_out(cmd, **options, default_env: false) - end - - it "should no longer set env['LC_ALL'] to nil when 'LC_ALL' not present" do - options = { env: { "HOME" => "/Users/morty" } } - expect(shell_out_obj).to receive(:__shell_out_command).with(cmd, options).and_return(true) - shell_out_obj.shell_out(cmd, **options, default_env: false) - end - end - - describe "and no env/environment option is present" do - it "should no longer add environment option and set environment['LC_ALL'] to nil" do - options = { user: "morty" } - expect(shell_out_obj).to receive(:__shell_out_command).with(cmd, options).and_return(true) - shell_out_obj.shell_out(cmd, **options, default_env: false) - end - end - end - - describe "when the last argument is not a Hash" do - it "should no longer add environment options and set environment['LC_ALL'] to nil" do - expect(shell_out_obj).to receive(:__shell_out_command).with(cmd).and_return(true) - shell_out_obj.shell_out(cmd, default_env: false) - end - end - end - - describe "Custom Resource timeouts" do - class CustomResource < Chef::Resource - provides :whatever - - property :timeout, Numeric - - action :install do - end - end - - let(:new_resource) { CustomResource.new("foo") } - let(:provider) { new_resource.provider_for_action(:install) } - - %i{shell_out shell_out!}.each do |method| - stubbed_method = (method == :shell_out) ? :shell_out_compacted : :shell_out_compacted! - it "#{method} defaults to 900 seconds" do - expect(provider).to receive(stubbed_method).with("foo", timeout: 900) - provider.send(method, "foo") - end - it "#{method} overrides the default timeout with its options" do - expect(provider).to receive(stubbed_method).with("foo", timeout: 1) - provider.send(method, "foo", timeout: 1) - end - it "#{method} overrides the new_resource.timeout with the timeout option" do - new_resource.timeout(99) - expect(provider).to receive(stubbed_method).with("foo", timeout: 1) - provider.send(method, "foo", timeout: 1) - end - it "#{method} defaults to 900 seconds and preserves options" do - expect(provider).to receive(stubbed_method).with("foo", env: nil, timeout: 900) - provider.send(method, "foo", env: nil) - end - it "#{method} overrides the default timeout with its options and preserves options" do - expect(provider).to receive(stubbed_method).with("foo", timeout: 1, env: nil) - provider.send(method, "foo", timeout: 1, env: nil) - end - it "#{method} overrides the new_resource.timeout with the timeout option and preseves options" do - new_resource.timeout(99) - expect(provider).to receive(stubbed_method).with("foo", timeout: 1, env: nil) - provider.send(method, "foo", timeout: 1, env: nil) - end - end - end - - describe "timeouts" do - let(:new_resource) { Chef::Resource::Package.new("foo") } - let(:provider) { new_resource.provider_for_action(:install) } - - %i{shell_out shell_out!}.each do |method| - stubbed_method = (method == :shell_out) ? :shell_out_compacted : :shell_out_compacted! - it "#{method} defaults to 900 seconds" do - expect(provider).to receive(stubbed_method).with("foo", timeout: 900) - provider.send(method, "foo") - end - it "#{method} overrides the default timeout with its options" do - expect(provider).to receive(stubbed_method).with("foo", timeout: 1) - provider.send(method, "foo", timeout: 1) - end - it "#{method} overrides the new_resource.timeout with the timeout option" do - new_resource.timeout(99) - expect(provider).to receive(stubbed_method).with("foo", timeout: 1) - provider.send(method, "foo", timeout: 1) - end - it "#{method} defaults to 900 seconds and preserves options" do - expect(provider).to receive(stubbed_method).with("foo", env: nil, timeout: 900) - provider.send(method, "foo", env: nil) - end - it "#{method} overrides the default timeout with its options and preserves options" do - expect(provider).to receive(stubbed_method).with("foo", timeout: 1, env: nil) - provider.send(method, "foo", timeout: 1, env: nil) - end - it "#{method} overrides the new_resource.timeout with the timeout option and preseves options" do - new_resource.timeout(99) - expect(provider).to receive(stubbed_method).with("foo", timeout: 1, env: nil) - provider.send(method, "foo", timeout: 1, env: nil) - end end end end diff --git a/spec/unit/provider/package/rubygems_spec.rb b/spec/unit/provider/package/rubygems_spec.rb index 7153d6be3e..13a1338251 100644 --- a/spec/unit/provider/package/rubygems_spec.rb +++ b/spec/unit/provider/package/rubygems_spec.rb @@ -17,15 +17,6 @@ # limitations under the License. # -module GemspecBackcompatCreator - def gemspec(name, version) - if Gem::Specification.new.method(:initialize).arity == 0 - Gem::Specification.new { |s| s.name = name; s.version = version } - else - Gem::Specification.new(name, version) - end - end -end # this is a global variable we construct of the highest rspec-core version which is installed, using APIs which # will break out of the bundle -- and done this way so that we can mock all these internal Gem APIs later... @@ -38,13 +29,29 @@ class RspecVersionString end end end +# This forces us to load the expected version string before any of our tests (in the entire suite) +# execute, ensuring that it's a 'clean' result for the environment. RspecVersionString.rspec_version_string + require "spec_helper" require "ostruct" + +describe "here we go" do + it "should pass now" do + # Uncommenting Gem.dir below does not let it pass, interestingly - though the same + # action in a different example, located in a different file, will 'Fix' it. + # # In other cases (like the same thing done in the hacked supermarket_share_spec + # #) Referring to Gem.paths does fix it. AT time of invocation, Gem's @paths is not + # initialized in those cases - but it is already initialized when we run this example, + # so the corruption has already occurred by the time we're here. + #Gem.dir + end +end +# end + describe Chef::Provider::Package::Rubygems::CurrentGemEnvironment do - include GemspecBackcompatCreator let(:logger) { double("Mixlib::Log::Child").as_null_object } before do @@ -53,1132 +60,15 @@ describe Chef::Provider::Package::Rubygems::CurrentGemEnvironment do WebMock.disable_net_connect! end - - it "determines the gem paths from the in memory rubygems" do - expect(@gem_env.gem_paths).to eq(Gem.path) - end - - it "determines the installed versions of gems from Gem.source_index" do - gems = [gemspec("rspec-core", Gem::Version.new("1.2.9")), gemspec("rspec-core", Gem::Version.new("1.3.0"))] - if Gem::Version.new(Gem::VERSION) >= Gem::Version.new("2.7") - expect(Gem::Specification).to receive(:dirs).and_return(["/path/to/gems/specifications", "/another/path/to/gems/specifications"]) - expect(Gem::Specification).to receive(:installed_stubs).with(["/path/to/gems/specifications", "/another/path/to/gems/specifications"], "rspec-core-*.gemspec").and_return(gems) - else # >= Rubygems 1.8 behavior - expect(Gem::Specification).to receive(:find_all_by_name).with("rspec-core", Gem::Dependency.new("rspec-core").requirement).and_return(gems) - end - expect(@gem_env.installed_versions(Gem::Dependency.new("rspec-core", nil))).to eq(gems) - end - it "determines the installed versions of gems from the source index (part2: the unmockening)" do - expected = ["rspec-core", Gem::Version.new( RspecVersionString.rspec_version_string )] - actual = @gem_env.installed_versions(Gem::Dependency.new("rspec-core", nil)).map { |spec| [spec.name, spec.version] } - expect(actual).to include(expected) - end - - it "yields to a block with an alternate source list set" do - sources_in_block = nil - normal_sources = Gem.sources - begin - @gem_env.with_gem_sources("http://gems.example.org") do - sources_in_block = Gem.sources - raise "sources should be reset even in case of an error" - end - rescue RuntimeError - end - expect(sources_in_block).to eq(%w{http://gems.example.org}) - expect(Gem.sources).to eq(normal_sources) - end - - it "it doesnt alter the gem sources if none are set" do - sources_in_block = nil - normal_sources = Gem.sources - begin - @gem_env.with_gem_sources(nil) do - sources_in_block = Gem.sources - raise "sources should be reset even in case of an error" - end - rescue RuntimeError - end - expect(sources_in_block).to eq(normal_sources) - expect(Gem.sources).to eq(normal_sources) - end - - context "new default rubygems behavior" do - before do - Chef::Config[:rubygems_cache_enabled] = false - - dep_installer = Gem::DependencyInstaller.new - expect(dep_installer).not_to receive(:find_gems_with_sources) - allow(@gem_env).to receive(:dependency_installer).and_return(dep_installer) - end - - it "finds a matching gem candidate version on rubygems 2.0.0+" do - stub_request(:head, "https://index.rubygems.org/") - .to_return(status: 200, body: "", headers: {}) - stub_request(:get, "https://index.rubygems.org/info/sexp_processor") - .to_return(status: 200, body: File.binread(File.join(CHEF_SPEC_DATA, "rubygems.org", "sexp_processor-info"))) - stub_request(:get, "https://index.rubygems.org/quick/Marshal.4.8/sexp_processor-4.15.1.gemspec.rz") - .to_return(status: 200, body: File.binread(File.join(CHEF_SPEC_DATA, "rubygems.org", "sexp_processor-4.15.1.gemspec.rz"))) - - dep = Gem::Dependency.new("sexp_processor", ">= 0") - expect(@gem_env.candidate_version_from_remote(dep)).to be_kind_of(Gem::Version) - end - - it "gives the candidate version as nil if none is found" do - stub_request(:head, "https://index.rubygems.org/") - .to_return(status: 200, body: "", headers: {}) - stub_request(:get, "https://index.rubygems.org/info/nonexistent_gem") - .to_return(status: 200, body: File.binread(File.join(CHEF_SPEC_DATA, "rubygems.org", "nonexistent_gem-info"))) - - dep = Gem::Dependency.new("nonexistent_gem", ">= 0") - expect(@gem_env.candidate_version_from_remote(dep)).to be_nil - end - - it "finds a matching gem from a specific gemserver when explicit sources are given (to a server that doesn't respond to api requests)" do - stub_request(:head, "https://rubygems2.org/") - .to_return(status: 200, body: "", headers: {}) - stub_request(:get, "https://rubygems2.org/info/sexp_processor") - .to_return(status: 200, body: File.binread(File.join(CHEF_SPEC_DATA, "rubygems.org", "sexp_processor-info"))) - stub_request(:get, "https://rubygems2.org/quick/Marshal.4.8/sexp_processor-4.15.1.gemspec.rz") - .to_return(status: 200, body: File.binread(File.join(CHEF_SPEC_DATA, "rubygems.org", "sexp_processor-4.15.1.gemspec.rz"))) - - dep = Gem::Dependency.new("sexp_processor", ">= 0") - expect(@gem_env.candidate_version_from_remote(dep, "https://rubygems2.org")).to be_kind_of(Gem::Version) - end - end - - context "old rubygems caching behavior" do - before do - Chef::Config[:rubygems_cache_enabled] = true - - stub_request(:get, "https://rubygems.org/latest_specs.4.8.gz") - .to_return(status: 200, body: File.binread(File.join(CHEF_SPEC_DATA, "rubygems.org", "latest_specs.4.8.gz"))) - end - - it "finds a matching gem candidate version on rubygems 2.0.0+" do - stub_request(:get, "https://rubygems.org/quick/Marshal.4.8/sexp_processor-4.15.1.gemspec.rz") - .to_return(status: 200, body: File.binread(File.join(CHEF_SPEC_DATA, "rubygems.org", "sexp_processor-4.15.1.gemspec.rz"))) - - dep = Gem::Dependency.new("sexp_processor", ">= 0") - expect(@gem_env.candidate_version_from_remote(dep)).to be_kind_of(Gem::Version) - end - - it "gives the candidate version as nil if none is found" do - dep = Gem::Dependency.new("lksdjflksdjflsdkfj", ">= 0") - expect(@gem_env.candidate_version_from_remote(dep)).to be_nil - end - - it "finds a matching gem from a specific gemserver when explicit sources are given" do - stub_request(:get, "https://rubygems.org/quick/Marshal.4.8/sexp_processor-4.15.1.gemspec.rz") - .to_return(status: 200, body: File.binread(File.join(CHEF_SPEC_DATA, "rubygems.org", "sexp_processor-4.15.1.gemspec.rz"))) - - dep = Gem::Dependency.new("sexp_processor", ">= 0") - expect(@gem_env.candidate_version_from_remote(dep, "http://rubygems2.org")).to be_kind_of(Gem::Version) - end - end - - it "finds a matching candidate version from a .gem file when the path to the gem is supplied" do - location = CHEF_SPEC_DATA + "/gems/chef-integration-test-0.1.0.gem" - expect(@gem_env.candidate_version_from_file(Gem::Dependency.new("chef-integration-test", ">= 0"), location)).to eq(Gem::Version.new("0.1.0")) - expect(@gem_env.candidate_version_from_file(Gem::Dependency.new("chef-integration-test", ">= 0.2.0"), location)).to be_nil - end - - it "installs a gem with a hash of options for the dependency installer" do - dep_installer = Gem::DependencyInstaller.new - expect(@gem_env).to receive(:dependency_installer).with(install_dir: "/foo/bar").and_return(dep_installer) - expect(@gem_env).to receive(:with_gem_sources).with("http://gems.example.com").and_yield - expect(dep_installer).to receive(:install).with(Gem::Dependency.new("rspec", ">= 0")) - @gem_env.install(Gem::Dependency.new("rspec", ">= 0"), install_dir: "/foo/bar", sources: ["http://gems.example.com"]) - end - - it "builds an uninstaller for a gem with options set to avoid requiring user input" do - # default options for uninstaller should be: - # :ignore => true, :executables => true - expect(Gem::Uninstaller).to receive(:new).with("rspec", ignore: true, executables: true) - @gem_env.uninstaller("rspec") - end - - it "uninstalls all versions of a gem" do - uninstaller = double("gem uninstaller") - expect(uninstaller).to receive(:uninstall) - expect(@gem_env).to receive(:uninstaller).with("rspec", all: true).and_return(uninstaller) - @gem_env.uninstall("rspec") - end - - it "uninstalls a specific version of a gem" do - uninstaller = double("gem uninstaller") - expect(uninstaller).to receive(:uninstall) - expect(@gem_env).to receive(:uninstaller).with("rspec", version: "1.2.3").and_return(uninstaller) - @gem_env.uninstall("rspec", "1.2.3") - end - -end - -describe Chef::Provider::Package::Rubygems::AlternateGemEnvironment do - include GemspecBackcompatCreator - - before do - Chef::Provider::Package::Rubygems::AlternateGemEnvironment.gempath_cache.clear - Chef::Provider::Package::Rubygems::AlternateGemEnvironment.platform_cache.clear - @gem_env = Chef::Provider::Package::Rubygems::AlternateGemEnvironment.new("/usr/weird/bin/gem") - end - - it "determines the gem paths from shelling out to gem env" do - gem_env_output = ["/path/to/gems", "/another/path/to/gems"].join(File::PATH_SEPARATOR) - shell_out_result = OpenStruct.new(stdout: gem_env_output) - expect(@gem_env).to receive(:shell_out_compacted!).with("/usr/weird/bin/gem env gempath").and_return(shell_out_result) - expect(@gem_env.gem_paths).to eq(["/path/to/gems", "/another/path/to/gems"]) - end - - it "caches the gempaths by gem_binary" do - gem_env_output = ["/path/to/gems", "/another/path/to/gems"].join(File::PATH_SEPARATOR) - shell_out_result = OpenStruct.new(stdout: gem_env_output) - expect(@gem_env).to receive(:shell_out_compacted!).with("/usr/weird/bin/gem env gempath").and_return(shell_out_result) - expected = ["/path/to/gems", "/another/path/to/gems"] - expect(@gem_env.gem_paths).to eq(["/path/to/gems", "/another/path/to/gems"]) - expect(Chef::Provider::Package::Rubygems::AlternateGemEnvironment.gempath_cache["/usr/weird/bin/gem"]).to eq(expected) - end - - it "uses the cached result for gem paths when available" do - expect(@gem_env).not_to receive(:shell_out_compacted!) - expected = ["/path/to/gems", "/another/path/to/gems"] - Chef::Provider::Package::Rubygems::AlternateGemEnvironment.gempath_cache["/usr/weird/bin/gem"] = expected - expect(@gem_env.gem_paths).to eq(["/path/to/gems", "/another/path/to/gems"]) - end - - it "builds the gems source index from the gem paths" do - allow(@gem_env).to receive(:gem_paths).and_return(["/path/to/gems", "/another/path/to/gems"]) - @gem_env.gem_specification - expect(Gem::Specification.dirs).to eq([ "/path/to/gems/specifications", "/another/path/to/gems/specifications" ]) - end - it "determines the installed versions of gems from the source index" do - gems = [gemspec("rspec", Gem::Version.new("1.2.9")), gemspec("rspec", Gem::Version.new("1.3.0"))] - rspec_dep = Gem::Dependency.new("rspec", nil) - if Gem::Version.new(Gem::VERSION) >= Gem::Version.new("2.7") - allow(@gem_env).to receive(:gem_specification).and_return(Gem::Specification) - expect(Gem::Specification).to receive(:dirs).and_return(["/path/to/gems/specifications", "/another/path/to/gems/specifications"]) - expect(Gem::Specification).to receive(:installed_stubs).with(["/path/to/gems/specifications", "/another/path/to/gems/specifications"], "rspec-*.gemspec").and_return(gems) - else # >= rubygems 1.8 behavior - allow(@gem_env).to receive(:gem_specification).and_return(Gem::Specification) - expect(@gem_env.gem_specification).to receive(:find_all_by_name).with(rspec_dep.name, rspec_dep.requirement).and_return(gems) - end - expect(@gem_env.installed_versions(Gem::Dependency.new("rspec", nil))).to eq(gems) - end - - it "determines the installed versions of gems from the source index (part2: the unmockening)" do - allow($stdout).to receive(:write) - path_to_gem = if windows? - `where gem`.split[1] - else - `which gem`.strip - end - skip("cant find your gem executable") if path_to_gem.empty? - gem_env = Chef::Provider::Package::Rubygems::AlternateGemEnvironment.new(path_to_gem) + puts "BEFORE WE TEST" + puts "Gem.path: #{Gem.path}" + puts "Gem::Specification.dirs: #{Gem::Specification.dirs}" + puts "PathSupportGem.home: #{Gem::PathSupport.new(ENV).home}" expected = ["rspec-core", Gem::Version.new( RspecVersionString.rspec_version_string )] - actual = gem_env.installed_versions(Gem::Dependency.new("rspec-core", nil)).map { |s| [s.name, s.version] } + #require 'pry'; binding.pry + actual = @gem_env.installed_versions(Gem::Dependency.new("rspec-core", nil)).map { |spec| [spec.name, spec.version] } expect(actual).to include(expected) end - - it "detects when the target gem environment is the jruby platform" do - gem_env_out = <<~JRUBY_GEM_ENV - RubyGems Environment: - - RUBYGEMS VERSION: 1.3.6 - - RUBY VERSION: 1.8.7 (2010-05-12 patchlevel 249) [java] - - INSTALLATION DIRECTORY: /Users/you/.rvm/gems/jruby-1.5.0 - - RUBY EXECUTABLE: /Users/you/.rvm/rubies/jruby-1.5.0/bin/jruby - - EXECUTABLE DIRECTORY: /Users/you/.rvm/gems/jruby-1.5.0/bin - - RUBYGEMS PLATFORMS: - - ruby - - universal-java-1.6 - - GEM PATHS: - - /Users/you/.rvm/gems/jruby-1.5.0 - - /Users/you/.rvm/gems/jruby-1.5.0@global - - GEM CONFIGURATION: - - :update_sources => true - - :verbose => true - - :benchmark => false - - :backtrace => false - - :bulk_threshold => 1000 - - "install" => "--env-shebang" - - "update" => "--env-shebang" - - "gem" => "--no-rdoc --no-ri" - - :sources => ["https://rubygems.org/", "http://gems.github.com/"] - - REMOTE SOURCES: - - https://rubygems.org/ - - http://gems.github.com/ - JRUBY_GEM_ENV - expect(@gem_env).to receive(:shell_out_compacted!).with("/usr/weird/bin/gem env").and_return(double("jruby_gem_env", stdout: gem_env_out)) - expected = ["ruby", Gem::Platform.new("universal-java-1.6")] - expect(@gem_env.gem_platforms).to eq(expected) - # it should also cache the result - expect(Chef::Provider::Package::Rubygems::AlternateGemEnvironment.platform_cache["/usr/weird/bin/gem"]).to eq(expected) - end - - it "uses the cached result for gem platforms if available" do - expect(@gem_env).not_to receive(:shell_out_compacted!) - expected = ["ruby", Gem::Platform.new("universal-java-1.6")] - Chef::Provider::Package::Rubygems::AlternateGemEnvironment.platform_cache["/usr/weird/bin/gem"] = expected - expect(@gem_env.gem_platforms).to eq(expected) - end - - it "uses the current gem platforms when the target env is not jruby" do - gem_env_out = <<~RBX_GEM_ENV - RubyGems Environment: - - RUBYGEMS VERSION: 1.3.6 - - RUBY VERSION: 1.8.7 (2010-05-14 patchlevel 174) [x86_64-apple-darwin10.3.0] - - INSTALLATION DIRECTORY: /Users/ddeleo/.rvm/gems/rbx-1.0.0-20100514 - - RUBYGEMS PREFIX: /Users/ddeleo/.rvm/rubies/rbx-1.0.0-20100514 - - RUBY EXECUTABLE: /Users/ddeleo/.rvm/rubies/rbx-1.0.0-20100514/bin/rbx - - EXECUTABLE DIRECTORY: /Users/ddeleo/.rvm/gems/rbx-1.0.0-20100514/bin - - RUBYGEMS PLATFORMS: - - ruby - - x86_64-darwin-10 - - x86_64-rubinius-1.0 - - GEM PATHS: - - /Users/ddeleo/.rvm/gems/rbx-1.0.0-20100514 - - /Users/ddeleo/.rvm/gems/rbx-1.0.0-20100514@global - - GEM CONFIGURATION: - - :update_sources => true - - :verbose => true - - :benchmark => false - - :backtrace => false - - :bulk_threshold => 1000 - - :sources => ["https://rubygems.org/", "http://gems.github.com/"] - - "gem" => "--no-rdoc --no-ri" - - REMOTE SOURCES: - - https://rubygems.org/ - - http://gems.github.com/ - RBX_GEM_ENV - expect(@gem_env).to receive(:shell_out_compacted!).with("/usr/weird/bin/gem env").and_return(double("rbx_gem_env", stdout: gem_env_out)) - expect(@gem_env.gem_platforms).to eq(Gem.platforms) - expect(Chef::Provider::Package::Rubygems::AlternateGemEnvironment.platform_cache["/usr/weird/bin/gem"]).to eq(Gem.platforms) - end - - it "yields to a block while masquerading as a different gems platform" do - original_platforms = Gem.platforms - platforms_in_block = nil - begin - @gem_env.with_gem_platforms(["ruby", Gem::Platform.new("sparc64-java-1.7")]) do - platforms_in_block = Gem.platforms - raise "gem platforms should get set to the correct value even when an error occurs" - end - rescue RuntimeError - end - expect(platforms_in_block).to eq(["ruby", Gem::Platform.new("sparc64-java-1.7")]) - expect(Gem.platforms).to eq(original_platforms) - end - -end - -describe Chef::Provider::Package::Rubygems do - let(:target_version) { nil } - let(:gem_name) { "rspec-core" } - let(:gem_binary) { nil } - let(:bindir) { "/usr/bin" } - let(:options) { nil } - let(:source) { nil } - let(:include_default_source) { nil } - - let(:new_resource) do - new_resource = Chef::Resource::GemPackage.new(gem_name) - new_resource.version(target_version) - new_resource.gem_binary(gem_binary) if gem_binary - new_resource.options(options) if options - new_resource.source(source) if source - new_resource.include_default_source(include_default_source) - new_resource - end - - let(:current_resource) { nil } - - let(:provider) do - run_context = Chef::RunContext.new(Chef::Node.new, {}, Chef::EventDispatch::Dispatcher.new) - provider = Chef::Provider::Package::Rubygems.new(new_resource, run_context) - if current_resource - allow(provider).to receive(:load_current_resource) - provider.current_resource = current_resource - end - provider - end - - let(:gem_dep) { Gem::Dependency.new(gem_name, target_version) } - - before(:each) do - # We choose detect omnibus via RbConfig::CONFIG['bindir'] in Chef::Provider::Package::Rubygems.new - allow(RbConfig::CONFIG).to receive(:[]).with("bindir").and_return(bindir) - # Rubygems uses these two interally - allow(RbConfig::CONFIG).to receive(:[]).with("arch").and_call_original - allow(RbConfig::CONFIG).to receive(:[]).with("ruby_install_name").and_call_original - allow(File).to receive(:executable?).and_return false - allow(File).to receive(:executable?).with("#{bindir}/gem").and_return true - # XXX: we can't stub the provider object directly here because referencing it will create it and that - # will break later tests that want to test the initialize method, so we stub any instance - # (yet more evidence that initialize methods should be thin and do very little work) - allow_any_instance_of(Chef::Provider::Package::Rubygems).to receive(:needs_nodocument?).and_return true - end - - describe "when new_resource version is nil" do - let(:target_version) { nil } - - it "target_version_already_installed? should return false so that we can search for candidates" do - provider.load_current_resource - expect(provider.target_version_already_installed?(provider.current_resource.version, new_resource.version)).to be_falsey - end - - it "version_equals? should return false so that we can search for candidates" do - provider.load_current_resource - expect(provider.version_equals?(provider.current_resource.version, new_resource.version)).to be_falsey - end - end - - describe "when new_resource version is an rspec version" do - let(:current_version) { RspecVersionString.rspec_version_string } - let(:target_version) { current_version } - - it "triggers a gem configuration load so a later one will not stomp its config values" do - _ = provider - # ugly, is there a better way? - expect(Gem.instance_variable_get(:@configuration)).not_to be_nil - end - - it "uses the CurrentGemEnvironment implementation when no gem_binary_path is provided" do - expect(provider.gem_env).to be_a_kind_of(Chef::Provider::Package::Rubygems::CurrentGemEnvironment) - end - - context "when a gem_binary_path is provided" do - let(:gem_binary) { "/usr/weird/bin/gem" } - - it "uses the AlternateGemEnvironment implementation when a gem_binary_path is provided" do - expect(provider.gem_env.gem_binary_location).to eq(gem_binary) - end - - context "when you try to use a hash of install options" do - let(:options) { { fail: :burger } } - - it "smites you" do - expect { provider }.to raise_error(ArgumentError) - end - end - end - - context "when in omnibus opscode" do - let(:bindir) { "/opt/opscode/embedded/bin" } - - it "recognizes opscode as omnibus" do - expect(provider.is_omnibus?).to be true - end - end - - context "when in omnibus chefdk" do - let(:bindir) { "/opt/chefdk/embedded/bin" } - - it "recognizes chefdk as omnibus" do - expect(provider.is_omnibus?).to be true - end - end - - context "when in omnibus chef" do - let(:bindir) { "/opt/chef/embedded/bin" } - - it "recognizes chef as omnibus" do - expect(provider.is_omnibus?).to be true - end - - it "searches for a gem binary when running on Omnibus on Unix" do - platform_mock :unix do - allow(ENV).to receive(:[]).with("PATH").and_return("/usr/bin:/usr/sbin:/opt/chef/embedded/bin") - allow(ENV).to receive(:[]).with("PATHEXT").and_return(nil) - allow(File).to receive(:executable?).with("/usr/bin/gem").and_return(false) - allow(File).to receive(:executable?).with("/usr/sbin/gem").and_return(true) - allow(File).to receive(:executable?).with("/opt/chef/embedded/bin/gem").and_return(true) # should not get here - expect(provider.gem_env.gem_binary_location).to eq("/usr/sbin/gem") - end - end - - context "when on Windows" do - let(:bindir) { "d:/opscode/chef/embedded/bin" } - - it "searches for a gem binary when running on Omnibus on Windows" do - platform_mock :windows do - allow(ENV).to receive(:[]).with("PATH").and_return('C:\windows\system32;C:\windows;C:\Ruby186\bin') - allow(ENV).to receive(:[]).with("PATHEXT").and_return(nil) - allow(File).to receive(:executable?).with('C:\\windows\\system32/gem').and_return(false) - allow(File).to receive(:executable?).with('C:\\windows/gem').and_return(false) - allow(File).to receive(:executable?).with('C:\\Ruby186\\bin/gem').and_return(true) - allow(File).to receive(:executable?).with('d:\\opscode\\chef\\bin/gem').and_return(false) # should not get here - allow(File).to receive(:executable?).with('d:\\opscode\\chef\\bin/gem').and_return(false) # should not get here - allow(File).to receive(:executable?).with("d:/opscode/chef/embedded/bin/gem").and_return(false) # should not get here - expect(provider.gem_env.gem_binary_location).to eq('C:\Ruby186\bin/gem') - end - end - end - end - - it "converts the new resource into a gem dependency" do - expect(provider.gem_dependency).to eq(gem_dep) - end - - context "when the new resource is not the current version" do - let(:target_version) { "~> 9000.0.2" } - - it "converts the new resource into a gem dependency" do - expect(provider.gem_dependency).to eq(gem_dep) - end - end - - describe "when determining the currently installed version" do - before do - provider.load_current_resource - end - - it "sets the current version to the version specified by the new resource if that version is installed" do - expect(provider.current_resource.version).to eq(current_version) - end - - context "if the requested version is not installed" do - let(:target_version) { "9000.0.2" } - - it "sets the current version to the highest installed version if the requested version is not installed" do - expect(provider.current_resource.version).to eq(current_version) - end - end - - context "if the package is not currently installed" do - let(:gem_name) { "no-such-gem-should-exist-with-this-name" } - - it "leaves the current version at nil" do - expect(provider.current_resource.version).to be_nil - end - end - - end - - describe "when determining the candidate version to install" do - before do - provider.load_current_resource - end - - 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 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 source is from the rubygems_url" do - it "determines the candidate version by querying the remote gem servers" do - Chef::Config[:rubygems_url] = "https://mirror1/" - expect(provider.gem_env).to receive(:candidate_version_from_remote) - .with(gem_dep, "https://mirror1/") - .and_return(Gem::Version.new(target_version)) - expect(provider.candidate_version).to eq(target_version) - 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 - expect(provider.gem_env).to receive(:candidate_version_from_remote) - .with(gem_dep, source) - .and_return(Gem::Version.new(target_version)) - expect(provider.candidate_version).to eq(target_version) - end - - it "overwrites the config variable" do - new_resource.include_default_source false - Chef::Config[:rubygems_url] = "https://overridden" - expect(provider.gem_env).to receive(:candidate_version_from_remote) - .with(gem_dep, source) - .and_return(Gem::Version.new(target_version)) - expect(provider.candidate_version).to eq(target_version) - end - end - - context "when the requested source is an array" do - let(:source) { [ "https://mirror1", "https://mirror2" ] } - - it "determines the candidate version by querying the remote gem servers" do - expect(provider.gem_env).to receive(:candidate_version_from_remote) - .with(gem_dep, *source) - .and_return(Gem::Version.new(target_version)) - expect(provider.candidate_version).to eq(target_version) - end - - it "overwrites the config variable" do - new_resource.include_default_source false - Chef::Config[:rubygems_url] = "https://overridden" - expect(provider.gem_env).to receive(:candidate_version_from_remote) - .with(gem_dep, *source) - .and_return(Gem::Version.new(target_version)) - expect(provider.candidate_version).to eq(target_version) - end - end - - context "when the requested source is a file" do - let(:gem_name) { "chef-integration-test" } - let(:source) { CHEF_SPEC_DATA + "/gems/chef-integration-test-0.1.0.gem" } - let(:target_version) { ">= 0" } - - it "parses the gem's specification" do - expect(provider.candidate_version).to eq("0.1.0") - end - end - end - - describe "when installing a gem" do - let(:target_version) { "9000.0.2" } - let(:current_version) { nil } - let(:candidate_version) { "9000.0.2" } - let(:current_resource) do - current_resource = Chef::Resource::GemPackage.new(gem_name) - current_resource.version(current_version) - current_resource - end - - let(:version) { Gem::Version.new(candidate_version) } - - before do - expected_source = [ source ] - expected_source << "https://rubygems.org" if provider.include_default_source? - allow(provider.gem_env).to receive(:candidate_version_from_remote).with(gem_dep, *expected_source.flatten.compact).and_return(version) - end - - describe "in the current gem environment" do - it "installs the gem via the gems api when no explicit options are used" do - expect(provider.gem_env).to receive(:install).with(gem_dep, sources: [ "https://rubygems.org" ]) - provider.run_action(:install) - expect(new_resource).to be_updated_by_last_action - end - - context "when a remote source is provided" do - let(:source) { "http://gems.example.org" } - - it "installs the gem via the gems api" do - expect(provider.gem_env).to receive(:install).with(gem_dep, sources: [source]) - provider.run_action(:install) - expect(new_resource).to be_updated_by_last_action - end - end - - context "when source is a path" do - let(:source) { CHEF_SPEC_DATA + "/gems/chef-integration-test-0.1.0.gem" } - - it "installs the gem from file via the gems api" do - expect(provider.gem_env).to receive(:install).with(source) - provider.run_action(:install) - expect(new_resource).to be_updated_by_last_action - end - end - - context "when the gem name is a file path and source is nil" do - let(:gem_name) { CHEF_SPEC_DATA + "/gems/chef-integration-test-0.1.0.gem" } - - it "installs the gem from file via the gems api" do - expect(new_resource.source).to eq(gem_name) - expect(provider.gem_env).to receive(:install).with(gem_name) - provider.run_action(:install) - expect(new_resource).to be_updated_by_last_action - end - end - - # this catches 'gem_package "foo"' when "./foo" is a file in the cwd, and instead of installing './foo' it fetches the remote gem - it "installs the gem via the gems api, when the package has no file separator characters in it, but a matching file exists in cwd" do - allow(::File).to receive(:exist?).and_return(true) - new_resource.package_name("rspec-core") - expect(provider.gem_env).to receive(:install).with(gem_dep, sources: [ "https://rubygems.org" ]) - provider.run_action(:install) - expect(new_resource).to be_updated_by_last_action - end - - context "when options are provided as a String" do - let(:options) { "-i /alt/install/location" } - - it "installs the gem by shelling out when options are provided as a String" do - expected = "gem install rspec-core -q --no-document -v \"#{target_version}\" --source=https://rubygems.org #{options}" - expect(provider).to receive(:shell_out_compacted!).with(expected, env: nil, timeout: 900) - provider.run_action(:install) - expect(new_resource).to be_updated_by_last_action - end - - it "unmockening needs_nodocument?" do - expected = "gem install rspec-core -q --no-document -v \"#{target_version}\" --source=https://rubygems.org #{options}" - expect(provider).to receive(:needs_nodocument?).and_call_original - stub_const("Gem::VERSION", "3.0.0") - expect(provider).to receive(:shell_out_compacted!).with(expected, env: nil, timeout: 900) - provider.run_action(:install) - expect(new_resource).to be_updated_by_last_action - end - - it "when the rubygems_version is old it uses the old flags" do - expected = "gem install rspec-core -q --no-rdoc --no-ri -v \"#{target_version}\" --source=https://rubygems.org #{options}" - expect(provider).to receive(:needs_nodocument?).and_call_original - stub_const("Gem::VERSION", "2.8.0") - expect(provider).to receive(:shell_out_compacted!).with(expected, env: nil, timeout: 900) - provider.run_action(:install) - expect(new_resource).to be_updated_by_last_action - end - end - - context "when the Chef::Config[:rubygems_url] option is provided" do - let(:gem_binary) { "/foo/bar" } - - it "installs the gem" do - Chef::Config[:rubygems_url] = "https://mirror1" - expect(provider.gem_env).to receive(:candidate_version_from_remote).with(gem_dep, "https://mirror1").and_return(version) - expected = "#{gem_binary} install rspec-core -q --no-document -v \"#{target_version}\" --clear-sources --source=https://mirror1" - expect(provider).to receive(:shell_out_compacted!).with(expected, env: nil, timeout: 900) - provider.run_action(:install) - expect(new_resource).to be_updated_by_last_action - end - end - - context "when another source and binary are provided" do - let(:source) { "http://mirror.ops.rhcloud.com/mirror/ruby" } - let(:gem_binary) { "/foo/bar" } - - it "installs the gem" do - expected = "#{gem_binary} install rspec-core -q --no-document -v \"#{target_version}\" --clear-sources --source=#{source}" - expect(provider).to receive(:shell_out_compacted!).with(expected, env: nil, timeout: 900) - provider.run_action(:install) - expect(new_resource).to be_updated_by_last_action - end - - context "with include_default_source true" do - let(:include_default_source) { true } - - it "ignores the Chef::Config setting" do - expected = "#{gem_binary} install rspec-core -q --no-document -v \"#{target_version}\" --clear-sources --source=#{source} --source=https://rubygems.org" - expect(provider).to receive(:shell_out_compacted!).with(expected, env: nil, timeout: 900) - provider.run_action(:install) - expect(new_resource).to be_updated_by_last_action - end - end - - context "with include_default_source false" do - let(:include_default_source) { false } - - it "ignores the Chef::Config setting" do - Chef::Config[:rubygems_url] = "https://ignored" - expected = "#{gem_binary} install rspec-core -q --no-document -v \"#{target_version}\" --clear-sources --source=#{source}" - expect(provider).to receive(:shell_out_compacted!).with(expected, env: nil, timeout: 900) - provider.run_action(:install) - expect(new_resource).to be_updated_by_last_action - end - end - end - - context "when the source is an array" do - let(:source) { [ "https://mirror1" , "https://mirror2" ] } - let(:gem_binary) { "/foo/bar" } - - it "installs the gem with an array as an added source" do - expected = "#{gem_binary} install rspec-core -q --no-document -v \"#{target_version}\" --clear-sources --source=https://mirror1 --source=https://mirror2" - expect(provider).to receive(:shell_out_compacted!).with(expected, env: nil, timeout: 900) - provider.run_action(:install) - expect(new_resource).to be_updated_by_last_action - end - - context "with include_default_source true" do - let(:include_default_source) { true } - - it "installs the gem with rubygems as a source" do - expected = "#{gem_binary} install rspec-core -q --no-document -v \"#{target_version}\" --clear-sources --source=https://mirror1 --source=https://mirror2 --source=https://rubygems.org" - expect(provider).to receive(:shell_out_compacted!).with(expected, env: nil, timeout: 900) - provider.run_action(:install) - expect(new_resource).to be_updated_by_last_action - end - end - - context "with include_default_source false" do - let(:include_default_source) { false } - - it "ignores the Chef::Config setting" do - Chef::Config[:rubygems_url] = "https://ignored" - expected = "#{gem_binary} install rspec-core -q --no-document -v \"#{target_version}\" --clear-sources --source=https://mirror1 --source=https://mirror2" - expect(provider).to receive(:shell_out_compacted!).with(expected, env: nil, timeout: 900) - provider.run_action(:install) - expect(new_resource).to be_updated_by_last_action - end - end - end - - context "when clear_sources is set true and an explicit source is specified" do - let(:gem_binary) { "/foo/bar" } - let(:source) { "http://mirror.ops.rhcloud.com/mirror/ruby" } - - it "installs the gem" do - new_resource.clear_sources(true) - expected = "#{gem_binary} install rspec-core -q --no-document -v \"#{target_version}\" --clear-sources --source=#{source}" - expect(provider).to receive(:shell_out_compacted!).with(expected, env: nil, timeout: 900) - provider.run_action(:install) - expect(new_resource).to be_updated_by_last_action - end - end - - context "when clear_sources is set false and an explicit source is specified" do - let(:gem_binary) { "/foo/bar" } - let(:source) { "http://mirror.ops.rhcloud.com/mirror/ruby" } - - it "installs the gem" do - new_resource.clear_sources(false) - expected = "#{gem_binary} install rspec-core -q --no-document -v \"#{target_version}\" --source=#{source}" - expect(provider).to receive(:shell_out_compacted!).with(expected, env: nil, timeout: 900) - provider.run_action(:install) - expect(new_resource).to be_updated_by_last_action - end - end - - context "when no version is given" do - let(:target_version) { nil } - let(:options) { "-i /alt/install/location" } - - it "installs the gem by shelling out when options are provided but no version is given" do - expected = "gem install rspec-core -q --no-document -v \"#{candidate_version}\" --source=https://rubygems.org #{options}" - expect(provider).to receive(:shell_out_compacted!).with(expected, env: nil, timeout: 900) - provider.run_action(:install) - expect(new_resource).to be_updated_by_last_action - end - end - - context "when options are given as a Hash" do - let(:options) { { install_dir: "/alt/install/location" } } - - it "installs the gem via the gems api when options are given as a Hash" do - expect(provider.gem_env).to receive(:install).with(gem_dep, { sources: [ "https://rubygems.org" ] }.merge(options)) - provider.run_action(:install) - expect(new_resource).to be_updated_by_last_action - end - end - - describe "at a specific version" do - let(:target_version) { "9000.0.2" } - - it "installs the gem via the gems api" do - expect(provider.gem_env).to receive(:install).with(gem_dep, sources: [ "https://rubygems.org" ] ) - provider.run_action(:install) - expect(new_resource).to be_updated_by_last_action - end - end - - describe "at version specified with comparison operator" do - context "if current version satisfies requested version" do - let(:target_version) { ">=2.3.0" } - let(:current_version) { "2.3.3" } - - it "skips the install" 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 - let(:target_version) { "~>2.3.0" } - let(:current_version) { "2.3.3" } - - it "it matches an existing gem" 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 - - describe "in an alternate gem environment" do - let(:gem_binary) { "/usr/weird/bin/gem" } - - it "installs the gem by shelling out to gem install" do - expect(provider).to receive(:shell_out_compacted!).with("#{gem_binary} install rspec-core -q --no-document -v \"#{target_version}\" --source=https://rubygems.org", env: nil, timeout: 900) - provider.run_action(:install) - expect(new_resource).to be_updated_by_last_action - end - - it "unmockening needs_nodocument?" do - expect(provider).to receive(:needs_nodocument?).and_call_original - expect(provider.gem_env).to receive(:shell_out!).with("#{gem_binary} --version").and_return(instance_double(Mixlib::ShellOut, stdout: "3.0.0\n")) - expect(provider).to receive(:shell_out_compacted!).with("#{gem_binary} install rspec-core -q --no-document -v \"#{target_version}\" --source=https://rubygems.org", env: nil, timeout: 900) - provider.run_action(:install) - expect(new_resource).to be_updated_by_last_action - end - - it "when the rubygems_version is old it uses the old flags" do - expect(provider).to receive(:needs_nodocument?).and_call_original - expect(provider.gem_env).to receive(:shell_out!).with("#{gem_binary} --version").and_return(instance_double(Mixlib::ShellOut, stdout: "2.8.0\n")) - expect(provider).to receive(:shell_out_compacted!).with("#{gem_binary} install rspec-core -q --no-rdoc --no-ri -v \"#{target_version}\" --source=https://rubygems.org", env: nil, timeout: 900) - provider.run_action(:install) - expect(new_resource).to be_updated_by_last_action - end - - context "when source is a path" do - let(:source) { CHEF_SPEC_DATA + "/gems/chef-integration-test-0.1.0.gem" } - let(:target_version) { ">= 0" } - - it "installs the gem by shelling out to gem install" do - expect(provider).to receive(:shell_out_compacted!).with("#{gem_binary} install #{source} -q --no-document -v \"#{target_version}\"", env: nil, timeout: 900) - provider.run_action(:install) - expect(new_resource).to be_updated_by_last_action - end - end - - context "when the package is a path and source is nil" do - let(:gem_name) { CHEF_SPEC_DATA + "/gems/chef-integration-test-0.1.0.gem" } - let(:target_version) { ">= 0" } - - it "installs the gem from file by shelling out to gem install when the package is a path and the source is nil" do - expect(new_resource.source).to eq(gem_name) - expect(provider).to receive(:shell_out_compacted!).with("#{gem_binary} install #{gem_name} -q --no-document -v \"#{target_version}\"", env: nil, timeout: 900) - provider.run_action(:install) - expect(new_resource).to be_updated_by_last_action - end - end - end - - end - - describe "when uninstalling a gem" do - let(:gem_name) { "rspec" } - let(:current_version) { "1.2.3" } - let(:target_version) { nil } - - let(:current_resource) do - current_resource = Chef::Resource::GemPackage.new(gem_name) - current_resource.version(current_version) - current_resource - end - - describe "in the current gem environment" do - it "uninstalls via the api when no explicit options are used" do - # pre-reqs for action_remove to actually remove the package: - expect(provider.new_resource.version).to be_nil - expect(provider.current_resource.version).not_to be_nil - # the behavior we're testing: - expect(provider.gem_env).to receive(:uninstall).with("rspec", nil) - provider.action_remove - end - - context "when options are given as a Hash" do - let(:options) { { install_dir: "/alt/install/location" } } - - it "uninstalls via the api" do - # pre-reqs for action_remove to actually remove the package: - expect(provider.new_resource.version).to be_nil - expect(provider.current_resource.version).not_to be_nil - # the behavior we're testing: - expect(provider.gem_env).to receive(:uninstall).with("rspec", nil, options) - provider.action_remove - end - end - - context "when options are given as a String" do - let(:options) { "-i /alt/install/location" } - - it "uninstalls via the gem command" do - expect(provider).to receive(:shell_out_compacted!).with("gem uninstall rspec -q -x -I -a #{options}", env: nil, timeout: 900) - provider.action_remove - end - end - - context "when a version is provided" do - let(:target_version) { "1.2.3" } - - it "uninstalls a specific version of a gem" do - expect(provider.gem_env).to receive(:uninstall).with("rspec", "1.2.3") - provider.action_remove - end - end - end - - describe "in an alternate gem environment" do - let(:gem_binary) { "/usr/weird/bin/gem" } - - it "uninstalls via the gem command" do - expect(provider).to receive(:shell_out_compacted!).with("#{gem_binary} uninstall rspec -q -x -I -a", env: nil, timeout: 900) - provider.action_remove - end - end - end - end -end - -describe Chef::Provider::Package::Rubygems, "clear_sources?" do - let(:new_resource) do - Chef::Resource::GemPackage.new("foo") - end - - let(:provider) do - run_context = Chef::RunContext.new(Chef::Node.new, {}, Chef::EventDispatch::Dispatcher.new) - Chef::Provider::Package::Rubygems.new(new_resource, run_context) - end - - it "is false when clear_sources is unset" do - expect(provider.clear_sources?).to be false - end - - it "is false when clear_sources is set false" do - new_resource.clear_sources(false) - expect(provider.clear_sources?).to be false - end - - it "is true when clear_sources is set true" do - new_resource.clear_sources(true) - expect(provider.clear_sources?).to be true - end - - context "when a source is set" do - before do - new_resource.source("http://mirror.ops.rhcloud.com/mirror/ruby") - end - - it "is true when clear_sources is unset" do - expect(provider.clear_sources?).to be true - end - - it "is false when clear_sources is set false" do - new_resource.clear_sources(false) - expect(provider.clear_sources?).to be false - end - - it "is true when clear_sources is set true" do - new_resource.clear_sources(true) - expect(provider.clear_sources?).to be true - end - end - - context "when Chef::Config[:rubygems_url] is set" do - before do - Chef::Config.rubygems_url = "https://example.com/" - end - - it "is true when clear_sources is unset" do - expect(provider.clear_sources?).to be true - end - - it "is false when clear_sources is set false" do - new_resource.clear_sources(false) - expect(provider.clear_sources?).to be false - end - - it "is true when clear_sources is set true" do - new_resource.clear_sources(true) - expect(provider.clear_sources?).to be true - end - end -end - -describe Chef::Provider::Package::Rubygems, "include_default_source?" do - let(:new_resource) do - Chef::Resource::GemPackage.new("foo") - end - - let(:provider) do - run_context = Chef::RunContext.new(Chef::Node.new, {}, Chef::EventDispatch::Dispatcher.new) - Chef::Provider::Package::Rubygems.new(new_resource, run_context) - end - - it "is true when include_default_source is unset" do - expect(provider.include_default_source?).to be true - end - - it "is false when include_default_source is set false" do - new_resource.include_default_source(false) - expect(provider.include_default_source?).to be false - end - - it "is true when include_default_source is set true" do - new_resource.include_default_source(true) - expect(provider.include_default_source?).to be true - end - - context "when a source is set" do - before do - new_resource.source("http://mirror.ops.rhcloud.com/mirror/ruby") - end - - it "is false when include_default_source is unset" do - expect(provider.include_default_source?).to be false - end - - it "is false when include_default_source is set false" do - new_resource.include_default_source(false) - expect(provider.include_default_source?).to be false - end - - it "is true when include_default_source is set true" do - new_resource.include_default_source(true) - expect(provider.include_default_source?).to be true - end - end - - context "when Chef::Config[:rubygems_url] is set" do - before do - Chef::Config.rubygems_url = "https://example.com/" - end - - it "is true when include_default_source is unset" do - expect(provider.include_default_source?).to be true - end - - it "is false when include_default_source is set false" do - new_resource.include_default_source(false) - expect(provider.include_default_source?).to be false - end - - it "is true when include_default_source is set true" do - new_resource.include_default_source(true) - expect(provider.include_default_source?).to be true - end - end - - context "when clear_sources is set" do - before do - new_resource.clear_sources(true) - end - - it "is false when include_default_source is unset" do - expect(provider.include_default_source?).to be false - end - - it "is false when include_default_source is set false" do - new_resource.include_default_source(false) - expect(provider.include_default_source?).to be false - end - - it "is true when include_default_source is set true" do - new_resource.include_default_source(true) - expect(provider.include_default_source?).to be true - end - end end diff --git a/tasks/rspec.rb b/tasks/rspec.rb index 929e0f91b0..1d41914158 100644 --- a/tasks/rspec.rb +++ b/tasks/rspec.rb @@ -37,6 +37,20 @@ begin task default: :spec + + desc "Mini" + RSpec::Core::RakeTask.new(:mini) do |t| + t.verbose = true + t.rspec_opts= "--format doc" + t.pattern = + FileList["spec/unit/cookbook/gem_installer_spec.rb"] + + FileList["spec/unit/mixin/shell_out_spec.rb"] + + FileList["spec/unit/provider/package/rubygems_spec.rb"] + + # Does not HAVE To be the file, but I've already pared it down + # and commented some test variations that surface/hide the failure + FileList["spec/unit/knife/supermarket_share_spec.rb"] + end + task spec: :component_specs desc "Run all specs in spec directory" |