1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00
ruby--ruby/spec/bundler/bundler/bundler_spec.rb
2020-05-13 07:54:37 +09:00

476 lines
17 KiB
Ruby

# 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
context "on Rubies with a settable YAML engine", :if => defined?(YAML::ENGINE) do
context "with Syck as YAML::Engine" do
it "raises a GemspecError after YAML load throws ArgumentError" do
orig_yamler = YAML::ENGINE.yamler
YAML::ENGINE.yamler = "syck"
expect { subject }.to raise_error(Bundler::GemspecError)
YAML::ENGINE.yamler = orig_yamler
end
end
context "with Psych as YAML::Engine" do
it "raises a GemspecError after YAML load throws Psych::SyntaxError" do
orig_yamler = YAML::ENGINE.yamler
YAML::ENGINE.yamler = "psych"
expect { subject }.to raise_error(Bundler::GemspecError)
YAML::ENGINE.yamler = orig_yamler
end
end
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
env = {}
expect(Bundler).to receive(:use_system_gems?).and_return(false)
Bundler.send(:configure_gem_path, env)
expect(env.keys).to include("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 = <<EOF
It is a security vulnerability to allow your home directory to be world-writable, and bundler can not continue.
You should probably consider fixing this issue by running `chmod o-w ~` on *nix.
Please refer to https://ruby-doc.org/stdlib-2.1.2/libdoc/fileutils/rdoc/FileUtils.html#method-c-remove_entry_secure for details.
EOF
expect(bundler_ui).to receive(:warn).with(message)
expect { Bundler.send(:rm_rf, bundled_app) }.to raise_error(Bundler::PathError)
end
end
end
describe "#mkdir_p" do
it "creates a folder at the given path" do
install_gemfile <<-G
source "#{file_uri_for(gem_repo1)}"
gem "rack"
G
allow(Bundler).to receive(:root).and_return(bundled_app)
Bundler.mkdir_p(bundled_app.join("foo", "bar"))
expect(bundled_app.join("foo", "bar")).to exist
end
context "when mkdir_p requires sudo" do
it "creates a new folder using sudo" do
expect(Bundler).to receive(:requires_sudo?).and_return(true)
expect(Bundler).to receive(:sudo).and_return true
Bundler.mkdir_p(bundled_app.join("foo"))
end
end
context "with :no_sudo option" do
it "forces mkdir_p to not use sudo" do
expect(Bundler).to receive(:requires_sudo?).and_return(true)
expect(Bundler).to_not receive(:sudo)
Bundler.mkdir_p(bundled_app.join("foo"), :no_sudo => 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"))
message = <<EOF
`/home/oggy` is not a directory.
Bundler will use `/tmp/trulyrandom' as your home directory temporarily.
EOF
expect(Bundler.ui).to receive(:warn).with(message)
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"))
message = <<EOF
`/home/oggy` is not writable.
Bundler will use `/tmp/trulyrandom' as your home directory temporarily.
EOF
expect(Bundler.ui).to receive(:warn).with(message)
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"))
message = <<EOF
Your home directory is not set.
Bundler will use `/tmp/trulyrandom' as your home directory temporarily.
EOF
expect(Bundler.ui).to receive(:warn).with(message)
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