From 1f6095f912cb95e3fd0edb22823a4fb9aba20ce6 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 7 Jan 2014 16:12:12 -0800 Subject: [PATCH 1/5] core: Vagrant::CapabilityHost is a module for adding capabilities to things --- lib/vagrant/capability_host.rb | 175 ++++++++++++++++++++++ lib/vagrant/errors.rb | 16 ++ templates/locales/en.yml | 16 ++ test/unit/vagrant/capability_host_test.rb | 166 ++++++++++++++++++++ test/unit/vagrant/guest_test.rb | 118 --------------- 5 files changed, 373 insertions(+), 118 deletions(-) create mode 100644 lib/vagrant/capability_host.rb create mode 100644 test/unit/vagrant/capability_host_test.rb diff --git a/lib/vagrant/capability_host.rb b/lib/vagrant/capability_host.rb new file mode 100644 index 000000000..4c77930fa --- /dev/null +++ b/lib/vagrant/capability_host.rb @@ -0,0 +1,175 @@ +module Vagrant + # This module enables a class to host capabilities. Prior to being able + # to use any capabilities, the `initialize_capabilities!` method must be + # called. + # + # Capabilities allow small pieces of functionality to be plugged in using + # the Vagrant plugin model. Capabilities even allow for a certain amount + # of inheritence, where only a subset of capabilities may be implemented but + # a parent implements the rest. + # + # Capabilities are used heavily in Vagrant for host/guest interactions. For + # example, "mount_nfs_folder" is a guest-OS specific operation, so capabilities + # defer these operations to the guest. + module CapabilityHost + # Initializes the capability system by detecting the proper capability + # host to execute on and building the chain of capabilities to execute. + # + # @param [Symbol] host The host to use for the capabilities, or nil if + # we should auto-detect it. + # @param [Hash>] hosts Potential capability + # hosts. The key is the name of the host, value[0] is a class that + # implements `#detect?` and value[1] is a parent host (if any). + # @param [Hash>] capabilities The capabilities + # that are supported. The key is the host of the capability. Within that + # is a hash where the key is the name of the capability and the value + # is the class/module implementing it. + def initialize_capabilities!(host, hosts, capabilities, *args) + @cap_logger = Log4r::Logger.new("vagrant::capability_host::#{self.class}") + + if host && !hosts[host] + raise Errors::CapabilityHostExplicitNotDetected, value: host.to_s + end + + if !host + host = autodetect_capability_host(hosts) if !host + raise Errors::CapabilityHostNotDetected if !host + end + + if !hosts[host] + # This should never happen because the autodetect above uses the + # hosts hash to look up hosts. And if an explicit host is specified, + # we do another check higher up. + raise "Internal error. Host not found: #{host}" + end + + name = host + host_info = hosts[name] + host = host_info[0].new + chain = [] + chain << [name, host] + + # Build the proper chain of parents if there are any. + # This allows us to do "inheritance" of capabilities later + if host_info[1] + parent_name = host_info[1] + parent_info = hosts[parent_name] + while parent_info + chain << [parent_name, parent_info[0].new] + parent_name = parent_info[1] + parent_info = hosts[parent_name] + end + end + + @cap_host_chain = chain + @cap_args = args + @cap_caps = capabilities + true + end + + # Returns the chain of hosts that will be checked for capabilities. + # + # @return [Array>] + def capability_host_chain + @cap_host_chain + end + + # Tests whether the given capability is possible. + # + # @param [Symbol] cap_name Capability name + # @return [Boolean] + def capability?(cap_name) + !capability_module(cap_name.to_sym).nil? + end + + # Executes the capability with the given name, optionally passing more + # arguments onwards to the capability. If the capability returns a value, + # it will be returned. + # + # @param [Symbol] cap_name Name of the capability + def capability(cap_name, *args) + @cap_logger.info("Execute capability: #{cap_name} (#{@cap_host_chain[0][0]})") + cap_mod = capability_module(cap_name.to_sym) + if !cap_mod + raise Errors::CapabilityNotFound, + :cap => cap_name.to_s, + :host => @cap_host_chain[0][0].to_s + end + + cap_method = nil + begin + cap_method = cap_mod.method(cap_name) + rescue NameError + raise Errors::CapabilityInvalid, + :cap => cap_name.to_s, + :host => @cap_host_chain[0][0].to_s + end + + args = @cap_args + args + cap_method.call(*args) + end + + protected + + def autodetect_capability_host(hosts, *args) + @cap_logger.info("Autodetecting guest for machine: #{@machine}") + + # Get the mapping of hosts with the most parents. We start searching + # with the hosts with the most parents first. + parent_count = {} + hosts.each do |name, parts| + parent_count[name] = 0 + + parent = parts[1] + while parent + parent_count[name] += 1 + parent = hosts[parent] + parent = parent[1] if parent + end + end + + # Now swap around the mapping so that it is a mapping of + # count to the actual list of host names + parent_count_to_hosts = {} + parent_count.each do |name, count| + parent_count_to_hosts[count] ||= [] + parent_count_to_hosts[count] << name + end + + sorted_counts = parent_count_to_hosts.keys.sort.reverse + sorted_counts.each do |count| + parent_count_to_hosts[count].each do |name| + @cap_logger.debug("Trying: #{name}") + host_info = hosts[name] + host = host_info[0].new + + if host.detect?(*args) + @cap_logger.info("Detected: #{name}!") + return name + end + end + end + + return nil + end + + # Returns the registered module for a capability with the given name. + # + # @param [Symbol] cap_name + # @return [Module] + def capability_module(cap_name) + @cap_logger.debug("Searching for cap: #{cap_name}") + @cap_host_chain.each do |host_name, host| + @cap_logger.debug("Checking in: #{host_name}") + caps = @cap_caps[host_name] + + if caps && caps.has_key?(cap_name) + @cap_logger.debug("Found cap: #{cap_name} in #{host_name}") + return caps[cap_name] + end + end + + nil + end + end +end diff --git a/lib/vagrant/errors.rb b/lib/vagrant/errors.rb index ce21aef7b..fc30dfe1a 100644 --- a/lib/vagrant/errors.rb +++ b/lib/vagrant/errors.rb @@ -172,6 +172,22 @@ module Vagrant error_key(:bundler_error) end + class CapabilityHostExplicitNotDetected < VagrantError + error_key(:capability_host_explicit_not_detected) + end + + class CapabilityHostNotDetected < VagrantError + error_key(:capability_host_not_detected) + end + + class CapabilityInvalid < VagrantError + error_key(:capability_invalid) + end + + class CapabilityNotFound < VagrantError + error_key(:capability_not_found) + end + class CFEngineBootstrapFailed < VagrantError error_key(:cfengine_bootstrap_failed) end diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 384d1220d..6ca7b8e0b 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -261,6 +261,22 @@ en: issues. The error from Bundler is: %{message} + capability_host_explicit_not_detected: |- + The explicit capability host specified of '%{value}' could not be + found. + + This is an internal error that users should never see. Please report + a bug. + capability_host_not_detected: |- + The capability host could not be detected. This is an internal error + that users should never see. Please report a bug. + capability_invalid: |- + The capability '%{cap}' is invalid. This is an internal error that + users should never see. Please report a bug. + capability_not_found: |- + The capability '%{cap}' could not be found. This is an internal error + that users should never see. Please report a bug. + cfengine_bootstrap_failed: |- Failed to bootstrap CFEngine. Please see the output above to see what went wrong and address the issue. diff --git a/test/unit/vagrant/capability_host_test.rb b/test/unit/vagrant/capability_host_test.rb new file mode 100644 index 000000000..d35e81afb --- /dev/null +++ b/test/unit/vagrant/capability_host_test.rb @@ -0,0 +1,166 @@ +require File.expand_path("../../base", __FILE__) + +require "vagrant/capability_host" + +describe Vagrant::CapabilityHost do + subject do + Class.new do + extend Vagrant::CapabilityHost + end + end + + def detect_class(result) + Class.new do + define_method(:detect?) do + result + end + end + end + + def cap_instance(name, options=nil) + options ||= {} + + Class.new do + if !options[:corrupt] + define_method(name) do |*args| + raise "cap: #{name} #{args.inspect}" + end + end + end.new + end + + describe "#initialize_capabilities! and #capability_host_chain" do + it "raises an error if an explicit host is not found" do + expect { subject.initialize_capabilities!(:foo, {}, {}) }. + to raise_error(Vagrant::Errors::CapabilityHostExplicitNotDetected) + end + + it "raises an error if a host can't be detected" do + hosts = { + foo: [detect_class(false), nil], + bar: [detect_class(false), :foo], + } + + expect { subject.initialize_capabilities!(nil, hosts, {}) }. + to raise_error(Vagrant::Errors::CapabilityHostNotDetected) + end + + it "detects a basic child" do + hosts = { + foo: [detect_class(false), nil], + bar: [detect_class(true), nil], + baz: [detect_class(false), nil], + } + + subject.initialize_capabilities!(nil, hosts, {}) + + chain = subject.capability_host_chain + expect(chain.length).to eql(1) + expect(chain[0][0]).to eql(:bar) + end + + it "detects the host with the most parents (deepest) first" do + hosts = { + foo: [detect_class(true), nil], + bar: [detect_class(true), :foo], + baz: [detect_class(true), :bar], + foo2: [detect_class(true), nil], + bar2: [detect_class(true), :foo2], + } + + subject.initialize_capabilities!(nil, hosts, {}) + + chain = subject.capability_host_chain + expect(chain.length).to eql(3) + expect(chain.map(&:first)).to eql([:baz, :bar, :foo]) + end + + it "detects a forced host" do + hosts = { + foo: [detect_class(false), nil], + bar: [detect_class(false), nil], + baz: [detect_class(false), nil], + } + + subject.initialize_capabilities!(:bar, hosts, {}) + + chain = subject.capability_host_chain + expect(chain.length).to eql(1) + expect(chain[0][0]).to eql(:bar) + end + end + + describe "#capability?" do + before do + host = nil + hosts = { + foo: [detect_class(true), nil], + bar: [detect_class(true), :foo], + } + + caps = { + foo: { parent: Class.new }, + bar: { self: Class.new }, + } + + subject.initialize_capabilities!(host, hosts, caps) + end + + it "does not have a non-existent capability" do + expect(subject.capability?(:foo)).to be_false + end + + it "has capabilities of itself" do + expect(subject.capability?(:self)).to be_true + end + + it "has capabilities of parent" do + expect(subject.capability?(:parent)).to be_true + end + end + + describe "capability" do + let(:caps) { {} } + + def init + host = nil + hosts = { + foo: [detect_class(true), nil], + bar: [detect_class(true), :foo], + } + + subject.initialize_capabilities!(host, hosts, caps) + end + + it "executes the capability" do + caps[:bar] = { test: cap_instance(:test) } + init + + expect { subject.capability(:test) }. + to raise_error(RuntimeError, "cap: test []") + end + + it "executes the capability with arguments" do + caps[:bar] = { test: cap_instance(:test) } + init + + expect { subject.capability(:test, 1) }. + to raise_error(RuntimeError, "cap: test [1]") + end + + it "raises an exception if the capability doesn't exist" do + init + + expect { subject.capability(:what_is_this_i_dont_even) }. + to raise_error(Vagrant::Errors::CapabilityNotFound) + end + + it "raises an exception if the method doesn't exist on the module" do + caps[:bar] = { test_is_corrupt: cap_instance(:test_is_corrupt, corrupt: true) } + init + + expect { subject.capability(:test_is_corrupt) }. + to raise_error(Vagrant::Errors::CapabilityInvalid) + end + end +end diff --git a/test/unit/vagrant/guest_test.rb b/test/unit/vagrant/guest_test.rb index 4dcaab414..6dd9cbf8c 100644 --- a/test/unit/vagrant/guest_test.rb +++ b/test/unit/vagrant/guest_test.rb @@ -53,124 +53,6 @@ describe Vagrant::Guest do guests[name] = [guest, parent] end - describe "#capability" do - before :each do - register_guest(:foo, nil, true) - register_guest(:bar, :foo, true) - - subject.detect! - end - - it "executes the capability" do - register_capability(:bar, :test) - - expect { subject.capability(:test) }. - to raise_error(RuntimeError, "cap: test [machine]") - end - - it "executes the capability with arguments" do - register_capability(:bar, :test) - - expect { subject.capability(:test, 1) }. - to raise_error(RuntimeError, "cap: test [machine, 1]") - end - - it "raises an exception if the capability doesn't exist" do - expect { subject.capability(:what_is_this_i_dont_even) }. - to raise_error(Vagrant::Errors::GuestCapabilityNotFound) - end - - it "raises an exception if the method doesn't exist on the module" do - register_capability(:bar, :test_is_corrupt, corrupt: true) - - expect { subject.capability(:test_is_corrupt) }. - to raise_error(Vagrant::Errors::GuestCapabilityInvalid) - end - end - - describe "#capability?" do - before :each do - register_guest(:foo, nil, true) - register_guest(:bar, :foo, true) - - subject.detect! - end - - it "doesn't have unknown capabilities" do - subject.capability?(:what_is_this_i_dont_even).should_not be - end - - it "doesn't have capabilities registered to other guests" do - register_capability(:baz, :test) - - subject.capability?(:test).should_not be - end - - it "has capability of detected guest" do - register_capability(:bar, :test) - - subject.capability?(:test).should be - end - - it "has capability of parent guests" do - register_capability(:foo, :test) - - subject.capability?(:test).should be - end - end - - describe "#detect!" do - it "detects the first match" do - register_guest(:foo, nil, false) - register_guest(:bar, nil, true) - register_guest(:baz, nil, false) - - subject.detect! - subject.name.should == :bar - subject.chain.length.should == 1 - subject.chain[0][0].should == :bar - subject.chain[0][1].name.should == :bar - end - - it "detects those with the most parents first" do - register_guest(:foo, nil, true) - register_guest(:bar, :foo, true) - register_guest(:baz, :bar, true) - register_guest(:foo2, nil, true) - register_guest(:bar2, :foo2, true) - - subject.detect! - subject.name.should == :baz - subject.chain.length.should == 3 - subject.chain.map(&:first).should == [:baz, :bar, :foo] - subject.chain.map { |x| x[1] }.map(&:name).should == [:baz, :bar, :foo] - end - - it "detects the forced guest setting" do - register_guest(:foo, nil, false) - register_guest(:bar, nil, false) - - machine.config.vm.stub(:guest => :bar) - - subject.detect! - subject.name.should == :bar - end - - it "raises an exception if the forced guest can't be found" do - register_guest(:foo, nil, true) - - machine.config.vm.stub(:guest => :bar) - - expect { subject.detect! }. - to raise_error(Vagrant::Errors::GuestExplicitNotDetected) - end - - it "raises an exception if no guest can be detected" do - expect { subject.detect! }. - to raise_error(Vagrant::Errors::GuestNotDetected) - end - end - describe "#ready?" do before(:each) do register_guest(:foo, nil, true) From b15cb22e3e96b940a10a4053ac257039831b4b02 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 7 Jan 2014 16:14:16 -0800 Subject: [PATCH 2/5] core: errors expose their extra data --- lib/vagrant/errors.rb | 5 +++++ test/unit/vagrant/errors_test.rb | 8 +++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/vagrant/errors.rb b/lib/vagrant/errors.rb index fc30dfe1a..8654ac752 100644 --- a/lib/vagrant/errors.rb +++ b/lib/vagrant/errors.rb @@ -43,6 +43,9 @@ module Vagrant # error code, and the error key is used as a default message from # I18n. class VagrantError < StandardError + # This is extra data passed into the message for translation. + attr_accessor :extra_data + def self.error_key(key=nil, namespace=nil) define_method(:error_key) { key } error_namespace(namespace) if namespace @@ -57,6 +60,8 @@ module Vagrant end def initialize(message=nil, *args) + message ||= {} + @extra_data = message.dup message = { :_key => message } if message && !message.is_a?(Hash) message = { :_key => error_key, :_namespace => error_namespace }.merge(message || {}) diff --git a/test/unit/vagrant/errors_test.rb b/test/unit/vagrant/errors_test.rb index 66a2d1ea7..3c00b39c9 100644 --- a/test/unit/vagrant/errors_test.rb +++ b/test/unit/vagrant/errors_test.rb @@ -32,10 +32,16 @@ describe Vagrant::Errors::VagrantError do end end - subject { klass.new } + subject { klass.new(data: "yep") } it "should use the translation for the message" do subject.to_s.should == "foo" end + + it "should expose translation keys to the user" do + expect(subject.extra_data.length).to eql(1) + expect(subject.extra_data).to have_key(:data) + expect(subject.extra_data[:data]).to eql("yep") + end end end From 1f760b2c481f2f1305e90a0d50496114f147b3e2 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 7 Jan 2014 16:26:58 -0800 Subject: [PATCH 3/5] core: pass extra args to detect? when detecting capability host --- lib/vagrant/capability_host.rb | 2 +- test/unit/base.rb | 1 + .../shared/capability_helpers_context.rb | 21 +++++++++++ test/unit/vagrant/capability_host_test.rb | 37 +++++++++---------- 4 files changed, 40 insertions(+), 21 deletions(-) create mode 100644 test/unit/support/shared/capability_helpers_context.rb diff --git a/lib/vagrant/capability_host.rb b/lib/vagrant/capability_host.rb index 4c77930fa..c75f3c980 100644 --- a/lib/vagrant/capability_host.rb +++ b/lib/vagrant/capability_host.rb @@ -32,7 +32,7 @@ module Vagrant end if !host - host = autodetect_capability_host(hosts) if !host + host = autodetect_capability_host(hosts, *args) if !host raise Errors::CapabilityHostNotDetected if !host end diff --git a/test/unit/base.rb b/test/unit/base.rb index 4218abcbf..db530c2e4 100644 --- a/test/unit/base.rb +++ b/test/unit/base.rb @@ -14,6 +14,7 @@ require "unit/support/dummy_communicator" require "unit/support/dummy_provider" require "unit/support/shared/base_context" require "unit/support/shared/action_synced_folders_context" +require "unit/support/shared/capability_helpers_context" require "unit/support/shared/virtualbox_context" # Do not buffer output diff --git a/test/unit/support/shared/capability_helpers_context.rb b/test/unit/support/shared/capability_helpers_context.rb new file mode 100644 index 000000000..f336cc7c3 --- /dev/null +++ b/test/unit/support/shared/capability_helpers_context.rb @@ -0,0 +1,21 @@ +shared_context "capability_helpers" do + def detect_class(result) + Class.new do + define_method(:detect?) do + result + end + end + end + + def cap_instance(name, options=nil) + options ||= {} + + Class.new do + if !options[:corrupt] + define_method(name) do |*args| + raise "cap: #{name} #{args.inspect}" + end + end + end.new + end +end diff --git a/test/unit/vagrant/capability_host_test.rb b/test/unit/vagrant/capability_host_test.rb index d35e81afb..d460a649b 100644 --- a/test/unit/vagrant/capability_host_test.rb +++ b/test/unit/vagrant/capability_host_test.rb @@ -3,32 +3,14 @@ require File.expand_path("../../base", __FILE__) require "vagrant/capability_host" describe Vagrant::CapabilityHost do + include_context "capability_helpers" + subject do Class.new do extend Vagrant::CapabilityHost end end - def detect_class(result) - Class.new do - define_method(:detect?) do - result - end - end - end - - def cap_instance(name, options=nil) - options ||= {} - - Class.new do - if !options[:corrupt] - define_method(name) do |*args| - raise "cap: #{name} #{args.inspect}" - end - end - end.new - end - describe "#initialize_capabilities! and #capability_host_chain" do it "raises an error if an explicit host is not found" do expect { subject.initialize_capabilities!(:foo, {}, {}) }. @@ -45,6 +27,21 @@ describe Vagrant::CapabilityHost do to raise_error(Vagrant::Errors::CapabilityHostNotDetected) end + it "passes on extra args to the detect method" do + klass = Class.new do + def detect?(*args) + raise "detect: #{args.inspect}" + end + end + + hosts = { + foo: [klass, nil], + } + + expect { subject.initialize_capabilities!(nil, hosts, {}, 1, 2) }. + to raise_error(RuntimeError, "detect: [1, 2]") + end + it "detects a basic child" do hosts = { foo: [detect_class(false), nil], From 40babfc3f7cbbe9a0a0b4d95db833bd96f7c3a39 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 7 Jan 2014 16:27:12 -0800 Subject: [PATCH 4/5] core: modify Guest to use new CapabilityHost mixin --- lib/vagrant/guest.rb | 152 ++------------------------------ test/unit/vagrant/guest_test.rb | 43 +++------ 2 files changed, 18 insertions(+), 177 deletions(-) diff --git a/lib/vagrant/guest.rb b/lib/vagrant/guest.rb index 4cd719400..85a6eb8da 100644 --- a/lib/vagrant/guest.rb +++ b/lib/vagrant/guest.rb @@ -1,5 +1,7 @@ require "log4r" +require "vagrant/capability_host" + module Vagrant # This class handles guest-OS specific interactions with a machine. # It is primarily responsible for detecting the proper guest OS @@ -17,21 +19,13 @@ module Vagrant # This system allows for maximum flexibility and pluginability for doing # guest OS specific operations. class Guest - attr_reader :chain - - # The name of the guest OS. This is available after {#detect!} is - # called. - # - # @return [Symbol] - attr_reader :name + include CapabilityHost def initialize(machine, guests, capabilities) @logger = Log4r::Logger.new("vagrant::guest") @capabilities = capabilities - @chain = [] @guests = guests @machine = machine - @name = nil end # This will detect the proper guest OS for the machine and set up @@ -40,52 +34,7 @@ module Vagrant @logger.info("Detect guest for machine: #{@machine}") guest_name = @machine.config.vm.guest - if guest_name - @logger.info("Using explicit config.vm.guest value: #{guest_name}") - else - # No explicit guest was specified, so autodetect it. - guest_name = autodetect_guest - raise Errors::GuestNotDetected if !guest_name - end - - if !@guests[guest_name] - # This can happen if config.vm.guest was specified with a value - # that doesn't exist. - raise Errors::GuestExplicitNotDetected, value: guest_name.to_s - end - - @name = guest_name - @chain = guest_chain(@name) - end - - # Tests whether the guest has the named capability. - # - # @return [Boolean] - def capability?(cap_name) - !capability_module(cap_name.to_sym).nil? - end - - # Executes the capability with the given name, optionally passing - # more arguments onwards to the capability. - def capability(cap_name, *args) - @logger.info("Execute capability: #{cap_name} (#{@chain[0][0]})") - cap_mod = capability_module(cap_name.to_sym) - if !cap_mod - raise Errors::GuestCapabilityNotFound, - :cap => cap_name.to_s, - :guest => @chain[0][0].to_s - end - - cap_method = nil - begin - cap_method = cap_mod.method(cap_name) - rescue NameError - raise Errors::GuestCapabilityInvalid, - :cap => cap_name.to_s, - :guest => @chain[0][0].to_s - end - - cap_method.call(@machine, *args) + initialize_capabilities!(guest_name, @guests, @capabilities, @machine) end # This returns whether the guest is ready to work. If this returns @@ -94,98 +43,7 @@ module Vagrant # # @return [Boolean] def ready? - !@chain.empty? - end - - protected - - # Returns the registered module for a capability with the given name. - # - # @param [Symbol] cap_name - # @return [Module] - def capability_module(cap_name) - @logger.debug("Searching for cap: #{cap_name}") - @chain.each do |guest_name, guest| - @logger.debug("Checking in: #{guest_name}") - caps = @capabilities[guest_name] - - if caps && caps.has_key?(cap_name) - @logger.debug("Found cap: #{cap_name} in #{guest_name}") - return caps[cap_name] - end - end - - nil - end - - # This autodetects the guest to use and returns the name of the guest. - # This returns nil if the guest type could not be autodetected. - # - # @return [Symbol] - def autodetect_guest - @logger.info("Autodetecting guest for machine: #{@machine}") - - # Get the mapping of guests with the most parents. We start searching - # with the guests with the most parents first. - parent_count = {} - @guests.each do |name, parts| - parent_count[name] = 0 - - parent = parts[1] - while parent - parent_count[name] += 1 - parent = @guests[parent] - parent = parent[1] if parent - end - end - - # Now swap around the mapping so that it is a mapping of - # count to the actual list of guest names - parent_count_to_guests = {} - parent_count.each do |name, count| - parent_count_to_guests[count] ||= [] - parent_count_to_guests[count] << name - end - - sorted_counts = parent_count_to_guests.keys.sort.reverse - sorted_counts.each do |count| - parent_count_to_guests[count].each do |name| - @logger.debug("Trying: #{name}") - guest_info = @guests[name] - guest = guest_info[0].new - - if guest.detect?(@machine) - @logger.info("Detected: #{name}!") - return name - end - end - end - - return nil - end - - # This returns the complete chain for the given guest. - # - # @return [Array] - def guest_chain(name) - guest_info = @guests[name] - guest = guest_info[0].new - chain = [] - chain << [name, guest] - - # Build the proper chain of parents if there are any. - # This allows us to do "inheritance" of capabilities later - if guest_info[1] - parent_name = guest_info[1] - parent_info = @guests[parent_name] - while parent_info - chain << [parent_name, parent_info[0].new] - parent_name = parent_info[1] - parent_info = @guests[parent_name] - end - end - - return chain + !!capability_host_chain end end end diff --git a/test/unit/vagrant/guest_test.rb b/test/unit/vagrant/guest_test.rb index 6dd9cbf8c..0a7d76001 100644 --- a/test/unit/vagrant/guest_test.rb +++ b/test/unit/vagrant/guest_test.rb @@ -3,7 +3,7 @@ require "pathname" require File.expand_path("../../base", __FILE__) describe Vagrant::Guest do - include_context "unit" + include_context "capability_helpers" let(:capabilities) { {} } let(:guests) { {} } @@ -18,44 +18,27 @@ describe Vagrant::Guest do subject { described_class.new(machine, guests, capabilities) } - # This registers a capability with a specific guest - def register_capability(guest, capability, options=nil) - options ||= {} + describe "#detect!" do + it "auto-detects if no explicit guest name given" do + machine.config.vm.stub(guest: nil) + subject.should_receive(:initialize_capabilities!). + with(nil, guests, capabilities, machine) - cap = Class.new do - if !options[:corrupt] - define_method(capability) do |*args| - raise "cap: #{capability} #{args.inspect}" - end - end + subject.detect! end - capabilities[guest] ||= {} - capabilities[guest][capability] = cap.new - end + it "uses the explicit guest name if specified" do + machine.config.vm.stub(guest: :foo) + subject.should_receive(:initialize_capabilities!). + with(:foo, guests, capabilities, machine) - # This registers a guest with the class. - # - # @param [Symbol] name Name of the guest - # @param [Symbol] parent Name of the parent - # @param [Boolean] detect Whether or not to detect properly - def register_guest(name, parent, detect) - guest = Class.new(Vagrant.plugin("2", "guest")) do - define_method(:name) do - name - end - - define_method(:detect?) do |m| - detect - end + subject.detect! end - - guests[name] = [guest, parent] end describe "#ready?" do before(:each) do - register_guest(:foo, nil, true) + guests[:foo] = [detect_class(true), nil] end it "should not be ready by default" do From c1d56da1ca9e55bb12755d3c1f2e6607aedc6f6d Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 7 Jan 2014 16:29:05 -0800 Subject: [PATCH 5/5] core: get tests passing for changes --- test/unit/support/shared/capability_helpers_context.rb | 2 +- test/unit/vagrant/machine_test.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/unit/support/shared/capability_helpers_context.rb b/test/unit/support/shared/capability_helpers_context.rb index f336cc7c3..7db62d161 100644 --- a/test/unit/support/shared/capability_helpers_context.rb +++ b/test/unit/support/shared/capability_helpers_context.rb @@ -1,7 +1,7 @@ shared_context "capability_helpers" do def detect_class(result) Class.new do - define_method(:detect?) do + define_method(:detect?) do |*args| result end end diff --git a/test/unit/vagrant/machine_test.rb b/test/unit/vagrant/machine_test.rb index 9cb6fe6b8..ca60f078b 100644 --- a/test/unit/vagrant/machine_test.rb +++ b/test/unit/vagrant/machine_test.rb @@ -225,8 +225,8 @@ describe Vagrant::Machine do it "should return the configured guest" do result = instance.guest result.should be_kind_of(Vagrant::Guest) - result.ready?.should be - result.chain[0][0].should == :test + expect(result).to be_ready + expect(result.capability_host_chain[0][0]).to eql(:test) end end