# frozen_string_literal: true require "bundler" require "tmpdir" RSpec.describe Bundler do describe "#load_gemspec_uncached" do let(:app_gemspec_path) { tmp("test.gemspec") } subject { Bundler.load_gemspec_uncached(app_gemspec_path) } context "with incorrect YAML file" do before do File.open(app_gemspec_path, "wb") do |f| f.write strip_whitespace(<<-GEMSPEC) --- {:!00 ao=gu\g1= 7~f GEMSPEC end end it "catches YAML syntax errors" do expect { subject }.to raise_error(Bundler::GemspecError, /error while loading `test.gemspec`/) end end context "with correct YAML file", :if => defined?(Encoding) do it "can load a gemspec with unicode characters with default ruby encoding" do # spec_helper forces the external encoding to UTF-8 but that's not the # default until Ruby 2.0 verbose = $VERBOSE $VERBOSE = false encoding = Encoding.default_external Encoding.default_external = "ASCII" $VERBOSE = verbose File.open(app_gemspec_path, "wb") do |file| file.puts <<-GEMSPEC.gsub(/^\s+/, "") # -*- encoding: utf-8 -*- Gem::Specification.new do |gem| gem.author = "André the Giant" end GEMSPEC end expect(subject.author).to eq("André the Giant") verbose = $VERBOSE $VERBOSE = false Encoding.default_external = encoding $VERBOSE = verbose end end it "sets loaded_from" do app_gemspec_path.open("w") do |f| f.puts <<-GEMSPEC Gem::Specification.new do |gem| gem.name = "validated" end GEMSPEC end expect(subject.loaded_from).to eq(app_gemspec_path.expand_path.to_s) end context "validate is true" do subject { Bundler.load_gemspec_uncached(app_gemspec_path, true) } it "validates the specification" do app_gemspec_path.open("w") do |f| f.puts <<-GEMSPEC Gem::Specification.new do |gem| gem.name = "validated" end GEMSPEC end expect(Bundler.rubygems).to receive(:validate).with have_attributes(:name => "validated") subject end end context "with gemspec containing local variables" do before do File.open(app_gemspec_path, "wb") do |f| f.write strip_whitespace(<<-GEMSPEC) must_not_leak = true Gem::Specification.new do |gem| gem.name = "leak check" end GEMSPEC end end it "should not pollute the TOPLEVEL_BINDING" do subject expect(TOPLEVEL_BINDING.eval("local_variables")).to_not include(:must_not_leak) end end end describe "#which" do let(:executable) { "executable" } let(:path) do if Gem.win_platform? %w[C:/a C:/b C:/c C:/../d C:/e] else %w[/a /b c ../d /e] end end let(:expected) { "executable" } before do ENV["PATH"] = path.join(File::PATH_SEPARATOR) allow(File).to receive(:file?).and_return(false) allow(File).to receive(:executable?).and_return(false) if expected expect(File).to receive(:file?).with(expected).and_return(true) expect(File).to receive(:executable?).with(expected).and_return(true) end end subject { described_class.which(executable) } shared_examples_for "it returns the correct executable" do it "returns the expected file" do expect(subject).to eq(expected) end end it_behaves_like "it returns the correct executable" context "when the executable in inside a quoted path" do let(:expected) do if Gem.win_platform? "C:/e/executable" else "/e/executable" end end it_behaves_like "it returns the correct executable" end context "when the executable is not found" do let(:expected) { nil } it_behaves_like "it returns the correct executable" end end describe "configuration" do context "disable_shared_gems" do it "should unset GEM_PATH with empty string" do expect(Bundler).to receive(:use_system_gems?).and_return(false) Bundler.send(:configure_gem_path) expect(ENV["GEM_PATH"]).to eq "" end end end describe "#rm_rf" do context "the directory is world writable" do let(:bundler_ui) { Bundler.ui } it "should raise a friendly error" do allow(File).to receive(:exist?).and_return(true) allow(::Bundler::FileUtils).to receive(:remove_entry_secure).and_raise(ArgumentError) allow(File).to receive(:world_writable?).and_return(true) message = < true) end end end describe "#user_home" do context "home directory is set" do it "should return the user home" do path = "/home/oggy" allow(Bundler.rubygems).to receive(:user_home).and_return(path) allow(File).to receive(:directory?).with(path).and_return true allow(File).to receive(:writable?).with(path).and_return true expect(Bundler.user_home).to eq(Pathname(path)) end context "is not a directory" do it "should issue a warning and return a temporary user home" do path = "/home/oggy" allow(Bundler.rubygems).to receive(:user_home).and_return(path) allow(File).to receive(:directory?).with(path).and_return false allow(Bundler).to receive(:tmp).and_return(Pathname.new("/tmp/trulyrandom")) expect(Bundler.ui).to receive(:warn).with("`/home/oggy` is not a directory.\n") expect(Bundler.ui).to receive(:warn).with("Bundler will use `/tmp/trulyrandom' as your home directory temporarily.\n") expect(Bundler.user_home).to eq(Pathname("/tmp/trulyrandom")) end end context "is not writable" do let(:path) { "/home/oggy" } let(:dotbundle) { "/home/oggy/.bundle" } it "should issue a warning and return a temporary user home" do allow(Bundler.rubygems).to receive(:user_home).and_return(path) allow(File).to receive(:directory?).with(path).and_return true allow(File).to receive(:writable?).with(path).and_return false allow(File).to receive(:directory?).with(dotbundle).and_return false allow(Bundler).to receive(:tmp).and_return(Pathname.new("/tmp/trulyrandom")) expect(Bundler.ui).to receive(:warn).with("`/home/oggy` is not writable.\n") expect(Bundler.ui).to receive(:warn).with("Bundler will use `/tmp/trulyrandom' as your home directory temporarily.\n") expect(Bundler.user_home).to eq(Pathname("/tmp/trulyrandom")) end context ".bundle exists and have correct permissions" do it "should return the user home" do allow(Bundler.rubygems).to receive(:user_home).and_return(path) allow(File).to receive(:directory?).with(path).and_return true allow(File).to receive(:writable?).with(path).and_return false allow(File).to receive(:directory?).with(dotbundle).and_return true allow(File).to receive(:writable?).with(dotbundle).and_return true expect(Bundler.user_home).to eq(Pathname(path)) end end end end context "home directory is not set" do it "should issue warning and return a temporary user home" do allow(Bundler.rubygems).to receive(:user_home).and_return(nil) allow(Bundler).to receive(:tmp).and_return(Pathname.new("/tmp/trulyrandom")) expect(Bundler.ui).to receive(:warn).with("Your home directory is not set.\n") expect(Bundler.ui).to receive(:warn).with("Bundler will use `/tmp/trulyrandom' as your home directory temporarily.\n") expect(Bundler.user_home).to eq(Pathname("/tmp/trulyrandom")) end end end describe "#requires_sudo?" do let!(:tmpdir) { Dir.mktmpdir } let(:bundle_path) { Pathname("#{tmpdir}/bundle") } def clear_cached_requires_sudo return unless Bundler.instance_variable_defined?(:@requires_sudo_ran) Bundler.remove_instance_variable(:@requires_sudo_ran) Bundler.remove_instance_variable(:@requires_sudo) end before do clear_cached_requires_sudo allow(Bundler).to receive(:which).with("sudo").and_return("/usr/bin/sudo") allow(Bundler).to receive(:bundle_path).and_return(bundle_path) end after do FileUtils.rm_rf(tmpdir) clear_cached_requires_sudo end subject { Bundler.requires_sudo? } context "bundle_path doesn't exist" do it { should be false } context "and parent dir can't be written" do before do FileUtils.chmod(0o500, tmpdir) end it { should be true } end context "with unwritable files in a parent dir" do # Regression test for https://github.com/rubygems/bundler/pull/6316 # It doesn't matter if there are other unwritable files so long as # bundle_path can be created before do file = File.join(tmpdir, "unrelated_file") FileUtils.touch(file) FileUtils.chmod(0o400, file) end it { should be false } end end context "bundle_path exists" do before do FileUtils.mkdir_p(bundle_path) end it { should be false } context "and is unwritable" do before do FileUtils.chmod(0o500, bundle_path) end it { should be true } end end context "path writability" do before do FileUtils.mkdir_p("tmp/vendor/bundle") FileUtils.mkdir_p("tmp/vendor/bin_dir") end after do FileUtils.rm_rf("tmp/vendor/bundle") FileUtils.rm_rf("tmp/vendor/bin_dir") end context "writable paths" do it "should return false and display nothing" do allow(Bundler).to receive(:bundle_path).and_return(Pathname("tmp/vendor/bundle")) expect(Bundler.ui).to_not receive(:warn) expect(Bundler.requires_sudo?).to eq(false) end end context "unwritable paths" do before do FileUtils.touch("tmp/vendor/bundle/unwritable1.txt") FileUtils.touch("tmp/vendor/bundle/unwritable2.txt") FileUtils.touch("tmp/vendor/bin_dir/unwritable3.txt") FileUtils.chmod(0o400, "tmp/vendor/bundle/unwritable1.txt") FileUtils.chmod(0o400, "tmp/vendor/bundle/unwritable2.txt") FileUtils.chmod(0o400, "tmp/vendor/bin_dir/unwritable3.txt") end it "should return true and display warn message" do allow(Bundler).to receive(:bundle_path).and_return(Pathname("tmp/vendor/bundle")) bin_dir = Pathname("tmp/vendor/bin_dir/") # allow File#writable? to be called with args other than the stubbed on below allow(File).to receive(:writable?).and_call_original # fake make the directory unwritable allow(File).to receive(:writable?).with(bin_dir).and_return(false) allow(Bundler).to receive(:system_bindir).and_return(Pathname("tmp/vendor/bin_dir/")) message = <<-MESSAGE.chomp Following files may not be writable, so sudo is needed: tmp/vendor/bin_dir/ tmp/vendor/bundle/unwritable1.txt tmp/vendor/bundle/unwritable2.txt MESSAGE expect(Bundler.ui).to receive(:warn).with(message) expect(Bundler.requires_sudo?).to eq(true) end end end end context "user cache dir" do let(:home_path) { Pathname.new(ENV["HOME"]) } let(:xdg_data_home) { home_path.join(".local") } let(:xdg_cache_home) { home_path.join(".cache") } let(:xdg_config_home) { home_path.join(".config") } let(:bundle_user_home_default) { home_path.join(".bundle") } let(:bundle_user_home_custom) { xdg_data_home.join("bundle") } let(:bundle_user_cache_default) { bundle_user_home_default.join("cache") } let(:bundle_user_cache_custom) { xdg_cache_home.join("bundle") } let(:bundle_user_config_default) { bundle_user_home_default.join("config") } let(:bundle_user_config_custom) { xdg_config_home.join("bundle") } let(:bundle_user_plugin_default) { bundle_user_home_default.join("plugin") } let(:bundle_user_plugin_custom) { xdg_data_home.join("bundle").join("plugin") } describe "#user_bundle_path" do before do allow(Bundler.rubygems).to receive(:user_home).and_return(home_path) end it "should use the default home path" do expect(Bundler.user_bundle_path).to eq(bundle_user_home_default) expect(Bundler.user_bundle_path("home")).to eq(bundle_user_home_default) expect(Bundler.user_bundle_path("cache")).to eq(bundle_user_cache_default) expect(Bundler.user_cache).to eq(bundle_user_cache_default) expect(Bundler.user_bundle_path("config")).to eq(bundle_user_config_default) expect(Bundler.user_bundle_path("plugin")).to eq(bundle_user_plugin_default) end it "should use custom home path as root for other paths" do ENV["BUNDLE_USER_HOME"] = bundle_user_home_custom.to_s allow(Bundler.rubygems).to receive(:user_home).and_raise expect(Bundler.user_bundle_path).to eq(bundle_user_home_custom) expect(Bundler.user_bundle_path("home")).to eq(bundle_user_home_custom) expect(Bundler.user_bundle_path("cache")).to eq(bundle_user_home_custom.join("cache")) expect(Bundler.user_cache).to eq(bundle_user_home_custom.join("cache")) expect(Bundler.user_bundle_path("config")).to eq(bundle_user_home_custom.join("config")) expect(Bundler.user_bundle_path("plugin")).to eq(bundle_user_home_custom.join("plugin")) end it "should use all custom paths, except home" do ENV.delete("BUNDLE_USER_HOME") ENV["BUNDLE_USER_CACHE"] = bundle_user_cache_custom.to_s ENV["BUNDLE_USER_CONFIG"] = bundle_user_config_custom.to_s ENV["BUNDLE_USER_PLUGIN"] = bundle_user_plugin_custom.to_s expect(Bundler.user_bundle_path).to eq(bundle_user_home_default) expect(Bundler.user_bundle_path("home")).to eq(bundle_user_home_default) expect(Bundler.user_bundle_path("cache")).to eq(bundle_user_cache_custom) expect(Bundler.user_cache).to eq(bundle_user_cache_custom) expect(Bundler.user_bundle_path("config")).to eq(bundle_user_config_custom) expect(Bundler.user_bundle_path("plugin")).to eq(bundle_user_plugin_custom) end end end end