vaguerent/test/unit/vagrant/plugin/manager_test.rb
Chris Roberts 21c1bb5e05 Add support for caching solutions. Remove GEMRC modifications.
This pull request adds an enhancement to the internal Bundler class
to cache solution sets. This prevents Vagrant from generating a
solution for configured plugins on every run. Modifications to
the configured plugin list (global or local) will result in the
cached solution being invalidatd and resolved again.

Also included is the removal of the GEMRC modifications required
for Windows.
2020-02-03 07:43:17 -08:00

441 lines
13 KiB
Ruby

require "json"
require "pathname"
require "vagrant/plugin"
require "vagrant/plugin/manager"
require "vagrant/plugin/state_file"
require "vagrant/util/deep_merge"
require File.expand_path("../../../base", __FILE__)
describe Vagrant::Plugin::Manager do
include_context "unit"
let(:path) do
Pathname.new(Dir::Tmpname.create("vagrant-test-plugin-manager") {})
end
let(:bundler) { double("bundler") }
after do
path.unlink if path.file?
end
before do
allow(Vagrant::Bundler).to receive(:instance).and_return(bundler)
end
subject { described_class.new(path) }
describe "#globalize!" do
let(:plugins) { double("plugins") }
before do
allow(subject).to receive(:bundler_init)
allow(subject).to receive(:installed_plugins).and_return(plugins)
end
it "should init bundler with installed plugins" do
expect(subject).to receive(:bundler_init).with(plugins, anything)
subject.globalize!
end
it "should return installed plugins" do
expect(subject.globalize!).to eq(plugins)
end
end
describe "#localize!" do
let(:env) { double("env", local_data_path: local_data_path) }
let(:local_data_path) { double("local_data_path") }
let(:plugins) { double("plugins") }
let(:state_file) { double("state_file", path: double("state_file_path"), installed_plugins: plugins) }
before do
allow(Vagrant::Plugin::StateFile).to receive(:new).and_return(state_file)
allow(bundler).to receive(:environment_path=)
allow(local_data_path).to receive(:join).and_return(local_data_path)
allow(subject).to receive(:bundler_init)
end
context "without local data path defined" do
let(:local_data_path) { nil }
it "should not do any initialization" do
expect(subject).not_to receive(:bundler_init)
subject.localize!(env)
end
it "should return nil" do
expect(subject.localize!(env)).to be_nil
end
end
it "should run bundler initialization" do
expect(subject).to receive(:bundler_init).with(plugins, anything)
subject.localize!(env)
end
it "should return plugins" do
expect(subject.localize!(env)).to eq(plugins)
end
end
describe "#ready?" do
let(:plugins) { double("plugins") }
let(:env) { double("env", local_data_path: nil) }
before do
allow(subject).to receive(:bundler_init)
end
it "should be false by default" do
expect(subject.ready?).to be_falsey
end
it "should be false when only globalize! has been called" do
subject.globalize!
expect(subject.ready?).to be_falsey
end
it "should be false when only localize! has been called" do
subject.localize!(env)
expect(subject.ready?).to be_falsey
end
it "should be true when both localize! and globalize! have been called" do
subject.globalize!
subject.localize!(env)
expect(subject.ready?).to be_truthy
end
end
describe "#bundler_init" do
let(:plugins) { {"plugin_name" => {}} }
before do
allow(Vagrant).to receive(:plugins_init?).and_return(true)
allow(bundler).to receive(:init!)
end
it "should init the bundler instance with plugins" do
expect(bundler).to receive(:init!).with(plugins, anything)
subject.bundler_init(plugins)
end
it "should return nil" do
expect(subject.bundler_init(plugins)).to be_nil
end
context "with plugin init disabled" do
before { expect(Vagrant).to receive(:plugins_init?).and_return(false) }
it "should return nil" do
expect(subject.bundler_init(plugins)).to be_nil
end
it "should not init the bundler instance" do
expect(bundler).not_to receive(:init!).with(plugins)
subject.bundler_init(plugins)
end
end
end
describe "#plugin_installed?" do
let(:ready) { true }
let(:specs) { [] }
before do
allow(subject).to receive(:ready?).and_return(ready)
allow(subject).to receive(:installed_specs).and_return(specs)
end
context "when manager is ready" do
it "should return false when plugin is not found" do
expect(subject.plugin_installed?("vagrant-plugin")).to be_falsey
end
context "when plugin is installed" do
let(:specs) { [Gem::Specification.new("vagrant-plugin", "1.2.3")] }
it "should return true" do
expect(subject.plugin_installed?("vagrant-plugin")).to be_truthy
end
it "should return true when version matches installed version" do
expect(subject.plugin_installed?("vagrant-plugin", "1.2.3")).to be_truthy
end
it "should return true when version requirement is satisified by version" do
expect(subject.plugin_installed?("vagrant-plugin", "> 1.0")).to be_truthy
end
it "should return false when version requirement is not satisified by version" do
expect(subject.plugin_installed?("vagrant-plugin", "2.0")).to be_falsey
end
end
end
context "when manager is not ready" do
let(:ready) { false }
let(:plugins) { {} }
before { allow(subject).to receive(:installed_plugins).and_return(plugins) }
it "should check installed plugin data" do
expect(subject).to receive(:installed_plugins).and_return(plugins)
subject.plugin_installed?("vagrant-plugin")
end
it "should return false when plugin is not found" do
expect(subject.plugin_installed?("vagrant-plugin")).to be_falsey
end
context "when plugin is installed" do
let(:plugins) { {"vagrant-plugin" => {"installed_gem_version" => "1.2.3"}} }
it "should return true" do
expect(subject.plugin_installed?("vagrant-plugin")).to be_truthy
end
it "should return true when version matches installed version" do
expect(subject.plugin_installed?("vagrant-plugin", "1.2.3")).to be_truthy
end
it "should return true when version requirement is satisified by version" do
expect(subject.plugin_installed?("vagrant-plugin", "> 1.0")).to be_truthy
end
it "should return false when version requirement is not satisified by version" do
expect(subject.plugin_installed?("vagrant-plugin", "2.0")).to be_falsey
end
end
end
end
describe "#install_plugin" do
it "installs the plugin and adds it to the state file" do
specs = Array.new(5) { Gem::Specification.new }
specs[3].name = "foo"
expect(bundler).to receive(:install).once.with(any_args) { |plugins, local|
expect(plugins).to have_key("foo")
expect(local).to be_falsey
}.and_return(specs)
expect(bundler).to receive(:clean)
result = subject.install_plugin("foo")
# It should return the spec of the installed plugin
expect(result).to eql(specs[3])
# It should've added the plugin to the state
expect(subject.installed_plugins).to have_key("foo")
end
it "masks GemNotFound with our error" do
expect(bundler).to receive(:install).and_raise(Gem::GemNotFoundException)
expect { subject.install_plugin("foo") }.
to raise_error(Vagrant::Errors::PluginGemNotFound)
end
it "masks bundler errors with our own error" do
expect(bundler).to receive(:install).and_raise(Gem::InstallError)
expect { subject.install_plugin("foo") }.
to raise_error(Vagrant::Errors::BundlerError)
end
it "can install a local gem" do
name = "foo.gem"
version = "1.0"
local_spec = Gem::Specification.new
local_spec.name = "bar"
local_spec.version = version
expect(bundler).to receive(:install_local).with(name, {}).
ordered.and_return(local_spec)
expect(bundler).not_to receive(:install)
expect(bundler).to receive(:clean)
subject.install_plugin(name)
plugins = subject.installed_plugins
expect(plugins).to have_key("bar")
expect(plugins["bar"]["gem_version"]).to eql("1.0")
end
describe "installation options" do
let(:specs) do
specs = Array.new(5) { Gem::Specification.new }
specs[3].name = "foo"
specs
end
before do
allow(bundler).to receive(:install).and_return(specs)
end
it "installs a version with constraints" do
expect(bundler).to receive(:install).once.with(any_args) { |plugins, local|
expect(plugins).to have_key("foo")
expect(plugins["foo"]["gem_version"]).to eql(">= 0.1.0")
expect(local).to be_falsey
}.and_return(specs)
expect(bundler).to receive(:clean)
subject.install_plugin("foo", version: ">= 0.1.0")
plugins = subject.installed_plugins
expect(plugins).to have_key("foo")
expect(plugins["foo"]["gem_version"]).to eql(">= 0.1.0")
end
it "installs with an exact version but doesn't constrain" do
expect(bundler).to receive(:install).once.with(any_args) { |plugins, local|
expect(plugins).to have_key("foo")
expect(plugins["foo"]["gem_version"]).to eql("0.1.0")
expect(local).to be_falsey
}.and_return(specs)
expect(bundler).to receive(:clean)
subject.install_plugin("foo", version: "0.1.0")
plugins = subject.installed_plugins
expect(plugins).to have_key("foo")
expect(plugins["foo"]["gem_version"]).to eql("0.1.0")
end
end
end
describe "#uninstall_plugin" do
it "removes the plugin from the state" do
sf = Vagrant::Plugin::StateFile.new(path)
sf.add_plugin("foo")
# Sanity
expect(subject.installed_plugins).to have_key("foo")
# Test
expect(bundler).to receive(:clean).once.with({})
# Remove it
subject.uninstall_plugin("foo")
expect(subject.installed_plugins).to_not have_key("foo")
end
it "masks bundler errors with our own error" do
sf = Vagrant::Plugin::StateFile.new(path)
sf.add_plugin("foo")
expect(bundler).to receive(:clean).and_raise(Gem::InstallError)
expect { subject.uninstall_plugin("foo") }.
to raise_error(Vagrant::Errors::BundlerError)
end
context "with a system file" do
let(:systems_path) { temporary_file }
before do
systems_path.unlink
allow(described_class).to receive(:system_plugins_file).and_return(systems_path)
sf = Vagrant::Plugin::StateFile.new(systems_path)
sf.add_plugin("foo", version: "0.2.0")
sf.add_plugin("bar")
end
it "uninstalls the user plugin if it exists" do
sf = Vagrant::Plugin::StateFile.new(path)
sf.add_plugin("bar")
# Test
expect(bundler).to receive(:clean).once.with(anything)
# Remove it
subject.uninstall_plugin("bar")
plugins = subject.installed_plugins
expect(plugins["foo"]["system"]).to be(true)
end
it "raises an error if uninstalling a system gem" do
expect { subject.uninstall_plugin("bar") }.
to raise_error(Vagrant::Errors::PluginUninstallSystem)
end
end
end
describe "#update_plugins" do
it "masks bundler errors with our own error" do
expect(bundler).to receive(:update).and_raise(Gem::InstallError)
expect { subject.update_plugins([]) }.
to raise_error(Vagrant::Errors::BundlerError)
end
end
context "without state" do
describe "#installed_plugins" do
it "is empty initially" do
expect(subject.installed_plugins).to be_empty
end
end
end
context "with state" do
before do
sf = Vagrant::Plugin::StateFile.new(path)
sf.add_plugin("foo", version: "0.1.0")
end
describe "#installed_plugins" do
it "has the plugins" do
plugins = subject.installed_plugins
expect(plugins.length).to eql(1)
expect(plugins).to have_key("foo")
end
end
describe "#installed_specs" do
it "has the plugins" do
# We just add "i18n" because it is a dependency of Vagrant and
# we know it will be there.
sf = Vagrant::Plugin::StateFile.new(path)
sf.add_plugin("i18n")
specs = subject.installed_specs
expect(specs.length).to eql(1)
expect(specs.first.name).to eql("i18n")
end
end
context "with system plugins" do
let(:systems_path) { temporary_file }
before do
systems_path.unlink
allow(described_class).to receive(:system_plugins_file).and_return(systems_path)
sf = Vagrant::Plugin::StateFile.new(systems_path)
sf.add_plugin("foo", version: "0.2.0")
sf.add_plugin("bar")
end
describe "#installed_plugins" do
it "has the plugins" do
plugins = subject.installed_plugins
expect(plugins.length).to eql(2)
expect(plugins).to have_key("foo")
expect(plugins["foo"]["gem_version"]).to eq("0.1.0")
expect(plugins["foo"]["system"]).to be_truthy
expect(plugins).to have_key("bar")
expect(plugins["bar"]["system"]).to be(true)
end
end
end
end
end