diff --git a/lib/vagrant/plugin/v2/config.rb b/lib/vagrant/plugin/v2/config.rb index 533e00bce..a74eb670c 100644 --- a/lib/vagrant/plugin/v2/config.rb +++ b/lib/vagrant/plugin/v2/config.rb @@ -5,6 +5,13 @@ module Vagrant # V2. Any configuration key plugins for V2 should inherit from this # class. class Config + # This constant represents an unset value. This is useful so it is + # possible to know the difference between a configuration value that + # was never set, and a value that is nil (explicitly). Best practice + # is to initialize all variables to this value, then the {#merge} + # method below will "just work" in many cases. + UNSET_VALUE = Object.new + # This is called as a last-minute hook that allows the configuration # object to finalize itself before it will be put into use. This is # a useful place to do some defaults in the case the user didn't @@ -43,7 +50,9 @@ module Vagrant # configuration classes to still hold around internal state # that isn't propagated. if !key.to_s.start_with?("@__") - result.instance_variable_set(key, obj.instance_variable_get(key)) + # Don't set the value if it is the unset value, either. + value = obj.instance_variable_get(key) + result.instance_variable_set(key, value) if value != UNSET_VALUE end end end diff --git a/test/unit/vagrant/plugin/v2/command_test.rb b/test/unit/vagrant/plugin/v2/command_test.rb new file mode 100644 index 000000000..ce3faf139 --- /dev/null +++ b/test/unit/vagrant/plugin/v2/command_test.rb @@ -0,0 +1,142 @@ +require File.expand_path("../../../../base", __FILE__) +require 'optparse' + +describe Vagrant::Plugin::V2::Command do + describe "parsing options" do + let(:klass) do + Class.new(described_class) do + # Make the method public since it is normally protected + public :parse_options + end + end + + it "returns the remaining arguments" do + options = {} + opts = OptionParser.new do |o| + o.on("-f") do |f| + options[:f] = f + end + end + + result = klass.new(["-f", "foo"], nil).parse_options(opts) + + # Check the results + options[:f].should be + result.should == ["foo"] + end + + it "creates an option parser if none is given" do + result = klass.new(["foo"], nil).parse_options(nil) + result.should == ["foo"] + end + + ["-h", "--help"].each do |help_string| + it "returns nil and prints the help if '#{help_string}' is given" do + instance = klass.new([help_string], nil) + instance.should_receive(:safe_puts) + instance.parse_options(OptionParser.new).should be_nil + end + end + + it "raises an error if invalid options are given" do + instance = klass.new(["-f"], nil) + expect { instance.parse_options(OptionParser.new) }. + to raise_error(Vagrant::Errors::CLIInvalidOptions) + end + end + + describe "target VMs" do + let(:klass) do + Class.new(described_class) do + # Make the method public since it is normally protected + public :with_target_vms + end + end + + let(:environment) do + env = double("environment") + env.stub(:root_path => "foo") + env + end + + let(:instance) { klass.new([], environment) } + + it "should raise an exception if a root_path is not available" do + environment.stub(:root_path => nil) + + expect { instance.with_target_vms }. + to raise_error(Vagrant::Errors::NoEnvironmentError) + end + + it "should yield every VM in order is no name is given" do + foo_vm = double("foo") + foo_vm.stub(:name).and_return("foo") + + bar_vm = double("bar") + bar_vm.stub(:name).and_return("bar") + + environment.stub(:machine_names => [:foo, :bar]) + environment.stub(:machine).with(:foo, :virtualbox).and_return(foo_vm) + environment.stub(:machine).with(:bar, :virtualbox).and_return(bar_vm) + + vms = [] + instance.with_target_vms do |vm| + vms << vm + end + + vms.should == [foo_vm, bar_vm] + end + + it "raises an exception if the named VM doesn't exist" do + environment.stub(:machine_names => [:default]) + environment.stub(:machine).with(:foo, anything).and_return(nil) + + expect { instance.with_target_vms("foo") }. + to raise_error(Vagrant::Errors::VMNotFoundError) + end + + it "yields the given VM if a name is given" do + foo_vm = double("foo") + + environment.stub(:machine).with(:foo, :virtualbox).and_return(foo_vm) + + vms = [] + instance.with_target_vms("foo") { |vm| vms << vm } + vms.should == [foo_vm] + end + end + + describe "splitting the main and subcommand args" do + let(:instance) do + Class.new(described_class) do + # Make the method public since it is normally protected + public :split_main_and_subcommand + end.new(nil, nil) + end + + it "should work when given all 3 parts" do + result = instance.split_main_and_subcommand(["-v", "status", "-h", "-v"]) + result.should == [["-v"], "status", ["-h", "-v"]] + end + + it "should work when given only a subcommand and args" do + result = instance.split_main_and_subcommand(["status", "-h"]) + result.should == [[], "status", ["-h"]] + end + + it "should work when given only main flags" do + result = instance.split_main_and_subcommand(["-v", "-h"]) + result.should == [["-v", "-h"], nil, []] + end + + it "should work when given only a subcommand" do + result = instance.split_main_and_subcommand(["status"]) + result.should == [[], "status", []] + end + + it "works when there are other non-flag args after the subcommand" do + result = instance.split_main_and_subcommand(["-v", "box", "add", "-h"]) + result.should == [["-v"], "box", ["add", "-h"]] + end + end +end diff --git a/test/unit/vagrant/plugin/v2/communicator_test.rb b/test/unit/vagrant/plugin/v2/communicator_test.rb new file mode 100644 index 000000000..7abf2abc1 --- /dev/null +++ b/test/unit/vagrant/plugin/v2/communicator_test.rb @@ -0,0 +1,9 @@ +require File.expand_path("../../../../base", __FILE__) + +describe Vagrant::Plugin::V2::Communicator do + let(:machine) { Object.new } + + it "should not match by default" do + described_class.match?(machine).should_not be + end +end diff --git a/test/unit/vagrant/plugin/v2/config_test.rb b/test/unit/vagrant/plugin/v2/config_test.rb new file mode 100644 index 000000000..6e32c814c --- /dev/null +++ b/test/unit/vagrant/plugin/v2/config_test.rb @@ -0,0 +1,60 @@ +require File.expand_path("../../../../base", __FILE__) + +describe Vagrant::Plugin::V2::Config do + include_context "unit" + + let(:foo_class) do + Class.new(described_class) do + attr_accessor :one + attr_accessor :two + end + end + + let(:unset_value) { described_class.const_get("UNSET_VALUE") } + + describe "merging" do + it "should merge by default by simply copying each instance variable" do + one = foo_class.new + one.one = 2 + one.two = 1 + + two = foo_class.new + two.two = 5 + + result = one.merge(two) + result.one.should == 2 + result.two.should == 5 + end + + it "prefers any set value over an UNSET_VALUE" do + one = foo_class.new + one.one = 1 + one.two = 2 + + two = foo_class.new + two.one = unset_value + two.two = 5 + + result = one.merge(two) + result.one.should == 1 + result.two.should == 5 + end + + it "doesn't merge values that start with a double underscore" do + one = foo_class.new + one.one = 1 + one.two = 1 + one.instance_variable_set(:@__bar, "one") + + two = foo_class.new + two.two = 2 + two.instance_variable_set(:@__bar, "two") + + # Merge and verify + result = one.merge(two) + result.one.should == 1 + result.two.should == 2 + result.instance_variable_get(:@__bar).should be_nil + end + end +end diff --git a/test/unit/vagrant/plugin/v2/host_test.rb b/test/unit/vagrant/plugin/v2/host_test.rb new file mode 100644 index 000000000..b1822c19b --- /dev/null +++ b/test/unit/vagrant/plugin/v2/host_test.rb @@ -0,0 +1,5 @@ +require File.expand_path("../../../../base", __FILE__) + +describe Vagrant::Plugin::V2::Host do + # No tests. +end diff --git a/test/unit/vagrant/plugin/v2/manager_test.rb b/test/unit/vagrant/plugin/v2/manager_test.rb new file mode 100644 index 000000000..0929bc5f0 --- /dev/null +++ b/test/unit/vagrant/plugin/v2/manager_test.rb @@ -0,0 +1,114 @@ +require File.expand_path("../../../../base", __FILE__) + +describe Vagrant::Plugin::V2::Manager do + include_context "unit" + + let(:instance) { described_class.new } + + def plugin + p = Class.new(Vagrant.plugin("2")) + yield p + p + end + + it "should enumerate registered communicator classes" do + pA = plugin do |p| + p.communicator("foo") { "bar" } + end + + pB = plugin do |p| + p.communicator("bar") { "baz" } + end + + instance.register(pA) + instance.register(pB) + + instance.communicators.length.should == 2 + instance.communicators[:foo].should == "bar" + instance.communicators[:bar].should == "baz" + end + + it "should enumerate registered configuration classes" do + pA = plugin do |p| + p.config("foo") { "bar" } + end + + pB = plugin do |p| + p.config("bar") { "baz" } + end + + instance.register(pA) + instance.register(pB) + + instance.config.length.should == 2 + instance.config[:foo].should == "bar" + instance.config[:bar].should == "baz" + end + + it "should enumerate registered upgrade safe config classes" do + pA = plugin do |p| + p.config("foo", true) { "bar" } + end + + pB = plugin do |p| + p.config("bar") { "baz" } + end + + instance.register(pA) + instance.register(pB) + + instance.config_upgrade_safe.length.should == 1 + instance.config_upgrade_safe[:foo].should == "bar" + end + + it "should enumerate registered guest classes" do + pA = plugin do |p| + p.guest("foo") { "bar" } + end + + pB = plugin do |p| + p.guest("bar") { "baz" } + end + + instance.register(pA) + instance.register(pB) + + instance.guests.length.should == 2 + instance.guests[:foo].should == "bar" + instance.guests[:bar].should == "baz" + end + + it "should enumerate registered host classes" do + pA = plugin do |p| + p.host("foo") { "bar" } + end + + pB = plugin do |p| + p.host("bar") { "baz" } + end + + instance.register(pA) + instance.register(pB) + + instance.hosts.length.should == 2 + instance.hosts[:foo].should == "bar" + instance.hosts[:bar].should == "baz" + end + + it "should enumerate registered provider classes" do + pA = plugin do |p| + p.provider("foo") { "bar" } + end + + pB = plugin do |p| + p.provider("bar") { "baz" } + end + + instance.register(pA) + instance.register(pB) + + instance.providers.length.should == 2 + instance.providers[:foo].should == "bar" + instance.providers[:bar].should == "baz" + end +end diff --git a/test/unit/vagrant/plugin/v2/plugin_test.rb b/test/unit/vagrant/plugin/v2/plugin_test.rb new file mode 100644 index 000000000..c12cc32b0 --- /dev/null +++ b/test/unit/vagrant/plugin/v2/plugin_test.rb @@ -0,0 +1,278 @@ +require File.expand_path("../../../../base", __FILE__) + +describe Vagrant::Plugin::V2::Plugin do + after(:each) do + # We want to make sure that the registered plugins remains empty + # after each test. + described_class.manager.reset! + end + + it "should be able to set and get the name" do + plugin = Class.new(described_class) do + name "foo" + end + + plugin.name.should == "foo" + end + + it "should be able to set and get the description" do + plugin = Class.new(described_class) do + description "bar" + end + + plugin.description.should == "bar" + end + + describe "action hooks" do + it "should register action hooks" do + plugin = Class.new(described_class) do + action_hook("foo") { "bar" } + end + + hooks = plugin.action_hook("foo") + hooks.length.should == 1 + hooks[0].call.should == "bar" + end + end + + describe "commands" do + it "should register command classes" do + plugin = Class.new(described_class) do + command("foo") { "bar" } + end + + plugin.command[:foo].should == "bar" + end + + ["spaces bad", "sym^bols"].each do |bad| + it "should not allow bad command name: #{bad}" do + plugin = Class.new(described_class) + + expect { plugin.command(bad) {} }. + to raise_error(Vagrant::Plugin::V2::InvalidCommandName) + end + end + + it "should lazily register command classes" do + # Below would raise an error if the value of the command class was + # evaluated immediately. By asserting that this does not raise an + # error, we verify that the value is actually lazily loaded + plugin = nil + expect { + plugin = Class.new(described_class) do + command("foo") { raise StandardError, "FAIL!" } + end + }.to_not raise_error + + # Now verify when we actually get the command key that + # a proper error is raised. + expect { + plugin.command[:foo] + }.to raise_error(StandardError) + end + end + + describe "communicators" do + it "should register communicator classes" do + plugin = Class.new(described_class) do + communicator("foo") { "bar" } + end + + plugin.communicator[:foo].should == "bar" + end + + it "should lazily register communicator classes" do + # Below would raise an error if the value of the class was + # evaluated immediately. By asserting that this does not raise an + # error, we verify that the value is actually lazily loaded + plugin = nil + expect { + plugin = Class.new(described_class) do + communicator("foo") { raise StandardError, "FAIL!" } + end + }.to_not raise_error + + # Now verify when we actually get the configuration key that + # a proper error is raised. + expect { + plugin.communicator[:foo] + }.to raise_error(StandardError) + end + end + + describe "configuration" do + it "should register configuration classes" do + plugin = Class.new(described_class) do + config("foo") { "bar" } + end + + plugin.config[:foo].should == "bar" + end + + it "should lazily register configuration classes" do + # Below would raise an error if the value of the config class was + # evaluated immediately. By asserting that this does not raise an + # error, we verify that the value is actually lazily loaded + plugin = nil + expect { + plugin = Class.new(described_class) do + config("foo") { raise StandardError, "FAIL!" } + end + }.to_not raise_error + + # Now verify when we actually get the configuration key that + # a proper error is raised. + expect { + plugin.config[:foo] + }.to raise_error(StandardError) + end + end + + describe "easy commands" do + it "should register with the commands" do + plugin = Class.new(described_class) do + easy_command("foo") {} + end + + # Check that the command class subclasses the easy command base + plugin.command[:foo].should < Vagrant::Easy::CommandBase + end + end + + describe "guests" do + it "should register guest classes" do + plugin = Class.new(described_class) do + guest("foo") { "bar" } + end + + plugin.guest[:foo].should == "bar" + end + + it "should lazily register guest classes" do + # Below would raise an error if the value of the guest class was + # evaluated immediately. By asserting that this does not raise an + # error, we verify that the value is actually lazily loaded + plugin = nil + expect { + plugin = Class.new(described_class) do + guest("foo") { raise StandardError, "FAIL!" } + end + }.to_not raise_error + + # Now verify when we actually get the guest key that + # a proper error is raised. + expect { + plugin.guest[:foo] + }.to raise_error(StandardError) + end + end + + describe "hosts" do + it "should register host classes" do + plugin = Class.new(described_class) do + host("foo") { "bar" } + end + + plugin.host[:foo].should == "bar" + end + + it "should lazily register host classes" do + # Below would raise an error if the value of the host class was + # evaluated immediately. By asserting that this does not raise an + # error, we verify that the value is actually lazily loaded + plugin = nil + expect { + plugin = Class.new(described_class) do + host("foo") { raise StandardError, "FAIL!" } + end + }.to_not raise_error + + # Now verify when we actually get the host key that + # a proper error is raised. + expect { + plugin.host[:foo] + }.to raise_error(StandardError) + end + end + + describe "providers" do + it "should register provider classes" do + plugin = Class.new(described_class) do + provider("foo") { "bar" } + end + + plugin.provider[:foo].should == "bar" + end + + it "should lazily register provider classes" do + # Below would raise an error if the value of the config class was + # evaluated immediately. By asserting that this does not raise an + # error, we verify that the value is actually lazily loaded + plugin = nil + expect { + plugin = Class.new(described_class) do + provider("foo") { raise StandardError, "FAIL!" } + end + }.to_not raise_error + + # Now verify when we actually get the configuration key that + # a proper error is raised. + expect { + plugin.provider[:foo] + }.to raise_error(StandardError) + end + end + + describe "provisioners" do + it "should register provisioner classes" do + plugin = Class.new(described_class) do + provisioner("foo") { "bar" } + end + + plugin.provisioner[:foo].should == "bar" + end + + it "should lazily register provisioner classes" do + # Below would raise an error if the value of the config class was + # evaluated immediately. By asserting that this does not raise an + # error, we verify that the value is actually lazily loaded + plugin = nil + expect { + plugin = Class.new(described_class) do + provisioner("foo") { raise StandardError, "FAIL!" } + end + }.to_not raise_error + + # Now verify when we actually get the configuration key that + # a proper error is raised. + expect { + plugin.provisioner[:foo] + }.to raise_error(StandardError) + end + end + + describe "plugin registration" do + let(:manager) { described_class.manager } + + it "should have no registered plugins" do + manager.registered.should be_empty + end + + it "should register a plugin when a name is set" do + plugin = Class.new(described_class) do + name "foo" + end + + manager.registered.should == [plugin] + end + + it "should register a plugin only once" do + plugin = Class.new(described_class) do + name "foo" + name "bar" + end + + manager.registered.should == [plugin] + end + end +end diff --git a/test/unit/vagrant/plugin/v2/provider_test.rb b/test/unit/vagrant/plugin/v2/provider_test.rb new file mode 100644 index 000000000..7a7b11876 --- /dev/null +++ b/test/unit/vagrant/plugin/v2/provider_test.rb @@ -0,0 +1,18 @@ +require File.expand_path("../../../../base", __FILE__) + +describe Vagrant::Plugin::V2::Provider do + let(:machine) { Object.new } + let(:instance) { described_class.new(machine) } + + it "should return nil by default for actions" do + instance.action(:whatever).should be_nil + end + + it "should return nil by default for ssh info" do + instance.ssh_info.should be_nil + end + + it "should return nil by default for state" do + instance.state.should be_nil + end +end