aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--railties/CHANGELOG2
-rw-r--r--railties/lib/initializer.rb39
-rw-r--r--railties/lib/rails/plugin.rb83
-rw-r--r--railties/lib/rails/plugin/loader.rb230
-rw-r--r--railties/lib/rails/plugin/locator.rb81
-rw-r--r--railties/test/initializer_test.rb81
-rw-r--r--railties/test/plugin_loader_test.rb198
-rw-r--r--railties/test/plugin_locator_test.rb106
-rw-r--r--railties/test/plugin_test.rb130
-rw-r--r--railties/test/plugin_test_helper.rb23
10 files changed, 679 insertions, 294 deletions
diff --git a/railties/CHANGELOG b/railties/CHANGELOG
index 960ad59ef3..2ee6fac28e 100644
--- a/railties/CHANGELOG
+++ b/railties/CHANGELOG
@@ -1,5 +1,7 @@
*SVN*
+* Refactor Plugin Loader. Add plugin lib paths early, and add lots of tests. Closes #9795 [lazyatom]
+
* Added --skip-timestamps to generators that produce models #10036 [tpope]
* Update Prototype to 1.6.0 and script.aculo.us to 1.8.0. [sam, madrobby]
diff --git a/railties/lib/initializer.rb b/railties/lib/initializer.rb
index 0256ba96b2..ab6549164c 100644
--- a/railties/lib/initializer.rb
+++ b/railties/lib/initializer.rb
@@ -34,7 +34,7 @@ module Rails
# The set of loaded plugins.
attr_reader :loaded_plugins
-
+
# Runs the initializer. By default, this will invoke the #process method,
# which simply executes all of the initialization routines. Alternately,
# you can specify explicitly which initialization routine you want:
@@ -64,6 +64,7 @@ module Rails
# * #set_load_path
# * #require_frameworks
# * #set_autoload_paths
+ # * add_plugin_load_paths
# * #load_environment
# * #initialize_encoding
# * #initialize_database
@@ -83,9 +84,10 @@ module Rails
def process
check_ruby_version
set_load_path
-
+
require_frameworks
set_autoload_paths
+ add_plugin_load_paths
load_environment
initialize_encoding
@@ -161,14 +163,20 @@ module Rails
def add_support_load_paths
end
+ # Adds all load paths from plugins to the global set of load paths, so that
+ # code from plugins can be required (explicitly or automatically via Dependencies).
+ def add_plugin_load_paths
+ plugin_loader.add_plugin_load_paths
+ end
+
# Loads all plugins in <tt>config.plugin_paths</tt>. <tt>plugin_paths</tt>
# defaults to <tt>vendor/plugins</tt> but may also be set to a list of
# paths, such as
# config.plugin_paths = ["#{RAILS_ROOT}/lib/plugins", "#{RAILS_ROOT}/vendor/plugins"]
#
- # Each plugin discovered in <tt>plugin_paths</tt> is initialized:
- # * add its +lib+ directory, if present, to the beginning of the load path
- # * evaluate <tt>init.rb</tt> if present
+ # In the default implementation, as each plugin discovered in <tt>plugin_paths</tt> is initialized:
+ # * its +lib+ directory, if present, is added to the load path (immediately after the applications lib directory)
+ # * <tt>init.rb</tt> is evalutated, if present
#
# After all plugins are loaded, duplicates are removed from the load path.
# If an array of plugin names is specified in config.plugins, only those plugins will be loaded
@@ -178,13 +186,11 @@ module Rails
# if config.plugins ends contains :all then the named plugins will be loaded in the given order and all other
# plugins will be loaded in alphabetical order
def load_plugins
- configuration.plugin_locators.each do |locator|
- locator.new(self).each do |plugin|
- plugin.load
- end
- end
- ensure_all_registered_plugins_are_loaded!
- $LOAD_PATH.uniq!
+ plugin_loader.load_plugins
+ end
+
+ def plugin_loader
+ @plugin_loader ||= configuration.plugin_loader.new(self)
end
# Loads the environment specified by Configuration#environment_path, which
@@ -337,15 +343,6 @@ module Rails
end
end
- private
- def ensure_all_registered_plugins_are_loaded!
- unless configuration.plugins.nil?
- if configuration.plugins.detect {|plugin| plugin != :all && !loaded_plugins.include?( plugin)}
- missing_plugins = configuration.plugins - (loaded_plugins + [:all])
- raise LoadError, "Could not locate the following plugins: #{missing_plugins.to_sentence}"
- end
- end
- end
end
# The Configuration class holds all the parameters for the Initializer and
diff --git a/railties/lib/rails/plugin.rb b/railties/lib/rails/plugin.rb
new file mode 100644
index 0000000000..2feb90bf14
--- /dev/null
+++ b/railties/lib/rails/plugin.rb
@@ -0,0 +1,83 @@
+module Rails
+
+ # The Plugin class should be an object which provides the following methods:
+ #
+ # * +name+ - used during initialisation to order the plugin (based on name and
+ # the contents of <tt>config.plugins</tt>)
+ # * +valid?+ - returns true if this plugin can be loaded
+ # * +load_paths+ - each path within the returned array will be added to the $LOAD_PATH
+ # * +load+ - finally 'load' the plugin.
+ #
+ # These methods are expected by the Rails::Plugin::Locator and Rails::Plugin::Loader classes.
+ # The default implementation returns the <tt>lib</tt> directory as its </tt>load_paths</tt>,
+ # and evaluates <tt>init.rb</tt> when <tt>load</tt> is called.
+ class Plugin
+ include Comparable
+
+ attr_reader :directory, :name
+
+ def initialize(directory)
+ @directory = directory
+ @name = File.basename(@directory) rescue nil
+ @loaded = false
+ end
+
+ def valid?
+ File.directory?(directory) && (has_lib_directory? || has_init_file?)
+ end
+
+ # Returns a list of paths this plugin wishes to make available in $LOAD_PATH
+ def load_paths
+ report_nonexistant_or_empty_plugin! unless valid?
+ has_lib_directory? ? [lib_path] : []
+ end
+
+ # Evaluates a plugin's init.rb file
+ def load(initializer)
+ report_nonexistant_or_empty_plugin! unless valid?
+ evaluate_init_rb(initializer)
+ @loaded = true
+ end
+
+ def loaded?
+ @loaded
+ end
+
+ def <=>(other_plugin)
+ name <=> other_plugin.name
+ end
+
+ private
+
+ def report_nonexistant_or_empty_plugin!
+ raise LoadError, "Can not find the plugin named: #{name}"
+ end
+
+ def lib_path
+ File.join(directory, 'lib')
+ end
+
+ def init_path
+ File.join(directory, 'init.rb')
+ end
+
+ def has_lib_directory?
+ File.directory?(lib_path)
+ end
+
+ def has_init_file?
+ File.file?(init_path)
+ end
+
+ def evaluate_init_rb(initializer)
+ if has_init_file?
+ silence_warnings do
+ # Allow plugins to reference the current configuration object
+ config = initializer.configuration
+
+ eval(IO.read(init_path), binding, init_path)
+ end
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/railties/lib/rails/plugin/loader.rb b/railties/lib/rails/plugin/loader.rb
index 016bcc6c50..dcc62d50b6 100644
--- a/railties/lib/rails/plugin/loader.rb
+++ b/railties/lib/rails/plugin/loader.rb
@@ -1,146 +1,150 @@
+require "rails/plugin"
+
module Rails
- module Plugin
+ class Plugin
class Loader
- include Comparable
- attr_reader :initializer, :directory, :name
-
- class << self
- def load(*args)
- new(*args).load
- end
- end
-
- def initialize(initializer, directory)
+ attr_reader :initializer
+
+ # Creates a new Plugin::Loader instance, associated with the given
+ # Rails::Initializer. This default implementation automatically locates
+ # all plugins, and adds all plugin load paths, when it is created. The plugins
+ # are then fully loaded (init.rb is evaluated) when load_plugins is called.
+ #
+ # It is the loader's responsibilty to ensure that only the plugins specified
+ # in the configuration are actually loaded, and that the order defined
+ # is respected.
+ def initialize(initializer)
@initializer = initializer
- @directory = directory
- @name = File.basename(directory).to_sym
end
-
- def load
- return false if loaded?
- report_nonexistant_or_empty_plugin!
- add_to_load_path!
- register_plugin_as_loaded
- evaluate
- true
+
+ # Returns the plugins to be loaded, in the order they should be loaded.
+ def plugins
+ @plugins ||= all_plugins.select { |plugin| should_load?(plugin) }.sort { |p1, p2| order_plugins(p1, p2) }
end
-
- def loaded?
- initializer.loaded_plugins.include?(name)
+
+ # Returns all the plugins that could be found by the current locators.
+ def all_plugins
+ @all_plugins ||= locate_plugins
+ @all_plugins
end
-
- def plugin_path?
- File.directory?(directory) && (has_lib_directory? || has_init_file?)
+
+ def load_plugins
+ plugins.each do |plugin|
+ plugin.load(initializer)
+ register_plugin_as_loaded(plugin)
+ end
+ ensure_all_registered_plugins_are_loaded!
end
- def enabled?
- !explicit_plugin_loading_order? || registered?
- end
+ # Adds the load paths for every plugin into the $LOAD_PATH. Plugin load paths are
+ # added *after* the application's <tt>lib</tt> directory, to ensure that an application
+ # can always override code within a plugin.
+ #
+ # Plugin load paths are also added to Dependencies.load_paths, and Dependencies.load_once_paths.
+ def add_plugin_load_paths
+ plugins.each do |plugin|
+ plugin.load_paths.each do |path|
+ $LOAD_PATH.insert(application_lib_index + 1, path)
+ Dependencies.load_paths << path
+ Dependencies.load_once_paths << path
+ end
+ end
+ $LOAD_PATH.uniq!
+ end
- def explicitly_enabled?
- !explicit_plugin_loading_order? || explicitly_registered?
- end
+ protected
- def registered?
- explicit_plugin_loading_order? && registered_plugins_names_plugin?(name)
- end
+ # The locate_plugins method uses each class in config.plugin_locators to
+ # find the set of all plugins available to this Rails application.
+ def locate_plugins
+ configuration.plugin_locators.map { |locator|
+ locator.new(initializer).plugins
+ }.flatten
+ # TODO: sorting based on config.plugins
+ end
- def explicitly_registered?
- explicit_plugin_loading_order? && registered_plugins.include?(name)
- end
-
- def plugin_does_not_exist!(plugin_name = name)
- raise LoadError, "Can not find the plugin named: #{plugin_name}"
- end
-
- private
- # The plugins that have been explicitly listed with config.plugins. If this list is nil
- # then it means the client does not care which plugins or in what order they are loaded,
- # so we load all in alphabetical order. If it is an empty array, we load no plugins, if it is
- # non empty, we load the named plugins in the order specified.
- def registered_plugins
- config.plugins
+ def register_plugin_as_loaded(plugin)
+ initializer.loaded_plugins << plugin
end
-
- def registered_plugins_names_plugin?(plugin_name)
- registered_plugins.include?(plugin_name) || registered_plugins.include?(:all)
+
+ def configuration
+ initializer.configuration
end
- def explicit_plugin_loading_order?
- !registered_plugins.nil?
+ def should_load?(plugin)
+ # uses Plugin#name and Plugin#valid?
+ enabled?(plugin) && plugin.valid?
end
-
- def report_nonexistant_or_empty_plugin!
- plugin_does_not_exist! unless plugin_path?
+
+ def order_plugins(plugin_a, plugin_b)
+ if !explicit_plugin_loading_order?
+ plugin_a <=> plugin_b
+ else
+ if !explicitly_enabled?(plugin_a) && !explicitly_enabled?(plugin_b)
+ plugin_a <=> plugin_b
+ else
+ effective_order_of(plugin_a) <=> effective_order_of(plugin_b)
+ end
+ end
end
- def lib_path
- File.join(directory, 'lib')
+ def effective_order_of(plugin)
+ if explicitly_enabled?(plugin)
+ registered_plugin_names.index(plugin.name)
+ else
+ registered_plugin_names.index('all')
+ end
end
-
- def init_path
- File.join(directory, 'init.rb')
+
+ def application_lib_index
+ $LOAD_PATH.index(File.join(RAILS_ROOT, 'lib')) || 0
+ end
+
+ def enabled?(plugin)
+ !explicit_plugin_loading_order? || registered?(plugin)
end
-
- def has_lib_directory?
- File.directory?(lib_path)
+
+ def explicit_plugin_loading_order?
+ !registered_plugin_names.nil?
end
-
- def has_init_file?
- File.file?(init_path)
+
+ def registered?(plugin)
+ explicit_plugin_loading_order? && registered_plugins_names_plugin?(plugin)
end
-
- def add_to_load_path!
- # Add lib to load path *after* the application lib, to allow
- # application libraries to override plugin libraries.
- if has_lib_directory?
- application_lib_index = $LOAD_PATH.index(application_library_path) || 0
- $LOAD_PATH.insert(application_lib_index + 1, lib_path)
- Dependencies.load_paths << lib_path
- Dependencies.load_once_paths << lib_path
- end
+
+ def explicitly_enabled?(plugin)
+ !explicit_plugin_loading_order? || explicitly_registered?(plugin)
end
-
- def application_library_path
- File.join(RAILS_ROOT, 'lib')
+
+ def explicitly_registered?(plugin)
+ explicit_plugin_loading_order? && registered_plugin_names.include?(plugin.name)
end
-
- # Allow plugins to reference the current configuration object
- def config
- initializer.configuration
+
+ def registered_plugins_names_plugin?(plugin)
+ registered_plugin_names.include?(plugin.name) || registered_plugin_names.include?('all')
end
-
- def register_plugin_as_loaded
- initializer.loaded_plugins << name
+
+ # The plugins that have been explicitly listed with config.plugins. If this list is nil
+ # then it means the client does not care which plugins or in what order they are loaded,
+ # so we load all in alphabetical order. If it is an empty array, we load no plugins, if it is
+ # non empty, we load the named plugins in the order specified.
+ def registered_plugin_names
+ configuration.plugins ? configuration.plugins.map(&:to_s) : nil
end
-
- # Evaluate in init.rb
- def evaluate
- silence_warnings { eval(IO.read(init_path), binding, init_path) } if has_init_file?
+
+ def loaded?(plugin_name)
+ initializer.loaded_plugins.detect { |plugin| plugin.name == plugin_name.to_s }
end
-
- def <=>(other_plugin_loader)
+
+ def ensure_all_registered_plugins_are_loaded!
if explicit_plugin_loading_order?
- if non_existent_plugin = [self, other_plugin_loader].detect { |plugin| !registered_plugins_names_plugin?(plugin.name) }
- plugin_does_not_exist!(non_existent_plugin.name)
- end
-
- if !explicitly_enabled? && !other_plugin_loader.explicitly_enabled?
- name.to_s <=> other_plugin_loader.name.to_s
- elsif registered_plugins.include?(:all) && (!explicitly_enabled? || !other_plugin_loader.explicitly_enabled?)
- effective_index = explicitly_enabled? ? registered_plugins.index(name) : registered_plugins.index(:all)
- other_effective_index = other_plugin_loader.explicitly_enabled? ?
- registered_plugins.index(other_plugin_loader.name) : registered_plugins.index(:all)
-
- effective_index <=> other_effective_index
- else
- registered_plugins.index(name) <=> registered_plugins.index(other_plugin_loader.name)
+ if configuration.plugins.detect {|plugin| plugin != :all && !loaded?(plugin) }
+ missing_plugins = configuration.plugins - (plugins + [:all])
+ raise LoadError, "Could not locate the following plugins: #{missing_plugins.to_sentence}"
end
-
- else
- name.to_s <=> other_plugin_loader.name.to_s
end
end
+
end
end
end \ No newline at end of file
diff --git a/railties/lib/rails/plugin/locator.rb b/railties/lib/rails/plugin/locator.rb
index 6c4f2605bb..b27e904b12 100644
--- a/railties/lib/rails/plugin/locator.rb
+++ b/railties/lib/rails/plugin/locator.rb
@@ -1,15 +1,23 @@
module Rails
- module Plugin
+ class Plugin
+
+ # The Plugin::Locator class should be subclasses to provide custom plugin-finding
+ # abilities to Rails (i.e. loading plugins from Gems, etc). Each subclass should implement
+ # the <tt>located_plugins</tt> method, which return an array of Plugin objects that have been found.
class Locator
include Enumerable
+
attr_reader :initializer
def initialize(initializer)
@initializer = initializer
end
+ # This method should return all the plugins which this Plugin::Locator can find
+ # These will then be used by the current Plugin::Loader, which is responsible for actually
+ # loading the plugins themselves
def plugins
- located_plugins.select(&:enabled?).sort
+ raise "The `plugins' method must be defined by concrete subclasses of #{self.class}"
end
def each(&block)
@@ -19,41 +27,52 @@ module Rails
def plugin_names
plugins.map(&:name)
end
-
- private
- def located_plugins
- raise "The `located_plugins' method must be defined by concrete subclasses of #{self.class}"
- end
end
+ # The Rails::Plugin::FileSystemLocator will try to locate plugins by examining the directories
+ # the the paths given in configuration.plugin_paths. Any plugins that can be found are returned
+ # in a list.
+ #
+ # The criteria for a valid plugin in this case is found in Rails::Plugin#valid?, although
+ # other subclasses of Rails::Plugin::Locator can of course use different conditions.
class FileSystemLocator < Locator
- private
- def located_plugins
- initializer.configuration.plugin_paths.flatten.inject([]) do |plugins, path|
- plugins.concat locate_plugins_under(path)
- plugins
- end.flatten
- end
+
+ # Returns all the plugins which can be loaded in the filesystem, under the paths given
+ # by configuration.plugin_paths.
+ def plugins
+ initializer.configuration.plugin_paths.flatten.inject([]) do |plugins, path|
+ plugins.concat locate_plugins_under(path)
+ plugins
+ end.flatten
+ end
+
+ private
+
+ # Attempts to create a plugin from the given path. If the created plugin is valid?
+ # (see Rails::Plugin#valid?) then the plugin instance is returned; otherwise nil.
+ def create_plugin(path)
+ plugin = Rails::Plugin.new(path)
+ plugin.valid? ? plugin : nil
+ end
- # This starts at the base path looking for directories that pass the plugin_path? test of the Plugin::Loader.
- # Since plugins can be nested arbitrarily deep within an unspecified number of intermediary directories,
- # this method runs recursively until it finds a plugin directory.
- #
- # e.g.
- #
- # locate_plugins_under('vendor/plugins/acts/acts_as_chunky_bacon')
- # => 'acts_as_chunky_bacon'
- def locate_plugins_under(base_path)
- Dir.glob(File.join(base_path, '*')).inject([]) do |plugins, path|
- plugin_loader = initializer.configuration.plugin_loader.new(initializer, path)
- if plugin_loader.plugin_path? && plugin_loader.enabled?
- plugins << plugin_loader
- elsif File.directory?(path)
- plugins.concat locate_plugins_under(path)
- end
- plugins
+ # This starts at the base path looking for valid plugins (see Rails::Plugin#valid?).
+ # Since plugins can be nested arbitrarily deep within an unspecified number of intermediary
+ # directories, this method runs recursively until it finds a plugin directory, e.g.
+ #
+ # locate_plugins_under('vendor/plugins/acts/acts_as_chunky_bacon')
+ # => <Rails::Plugin name: 'acts_as_chunky_bacon' ... >
+ #
+ def locate_plugins_under(base_path)
+ Dir.glob(File.join(base_path, '*')).inject([]) do |plugins, path|
+ if plugin = create_plugin(path)
+ plugins << plugin
+ elsif File.directory?(path)
+ plugins.concat locate_plugins_under(path)
end
+ plugins
end
+ end
+
end
end
end \ No newline at end of file
diff --git a/railties/test/initializer_test.rb b/railties/test/initializer_test.rb
index 5707a63258..156f670e87 100644
--- a/railties/test/initializer_test.rb
+++ b/railties/test/initializer_test.rb
@@ -135,3 +135,84 @@ uses_mocha 'framework paths' do
end
end
end
+
+uses_mocha "Initializer plugin loading tests" do
+ require File.dirname(__FILE__) + '/plugin_test_helper'
+
+ class InitializerPluginLoadingTests < Test::Unit::TestCase
+ def setup
+ @configuration = Rails::Configuration.new
+ @configuration.plugin_paths << plugin_fixture_root_path
+ @initializer = Rails::Initializer.new(@configuration)
+ @valid_plugin_path = plugin_fixture_path('default/stubby')
+ @empty_plugin_path = plugin_fixture_path('default/empty')
+ end
+
+ def test_no_plugins_are_loaded_if_the_configuration_has_an_empty_plugin_list
+ only_load_the_following_plugins! []
+ @initializer.load_plugins
+ assert_equal [], @initializer.loaded_plugins
+ end
+
+ def test_only_the_specified_plugins_are_located_in_the_order_listed
+ plugin_names = [:plugin_with_no_lib_dir, :acts_as_chunky_bacon]
+ only_load_the_following_plugins! plugin_names
+ load_plugins!
+ assert_plugins plugin_names, @initializer.loaded_plugins
+ end
+
+ def test_all_plugins_are_loaded_when_registered_plugin_list_is_untouched
+ failure_tip = "It's likely someone has added a new plugin fixture without updating this list"
+ load_plugins!
+ assert_plugins [:a, :acts_as_chunky_bacon, :plugin_with_no_lib_dir, :stubby], @initializer.loaded_plugins, failure_tip
+ end
+
+ def test_all_plugins_loaded_when_all_is_used
+ plugin_names = [:stubby, :acts_as_chunky_bacon, :all]
+ only_load_the_following_plugins! plugin_names
+ load_plugins!
+ failure_tip = "It's likely someone has added a new plugin fixture without updating this list"
+ assert_plugins [:stubby, :acts_as_chunky_bacon, :a, :plugin_with_no_lib_dir], @initializer.loaded_plugins, failure_tip
+ end
+
+ def test_all_plugins_loaded_after_all
+ plugin_names = [:stubby, :all, :acts_as_chunky_bacon]
+ only_load_the_following_plugins! plugin_names
+ load_plugins!
+ failure_tip = "It's likely someone has added a new plugin fixture without updating this list"
+ assert_plugins [:stubby, :a, :plugin_with_no_lib_dir, :acts_as_chunky_bacon], @initializer.loaded_plugins, failure_tip
+ end
+
+ def test_plugin_names_may_be_strings
+ plugin_names = ['stubby', 'acts_as_chunky_bacon', :a, :plugin_with_no_lib_dir]
+ only_load_the_following_plugins! plugin_names
+ load_plugins!
+ failure_tip = "It's likely someone has added a new plugin fixture without updating this list"
+ assert_plugins plugin_names, @initializer.loaded_plugins, failure_tip
+ end
+
+ def test_registering_a_plugin_name_that_does_not_exist_raises_a_load_error
+ only_load_the_following_plugins! [:stubby, :acts_as_a_non_existant_plugin]
+ assert_raises(LoadError) do
+ load_plugins!
+ end
+ end
+
+ def test_should_ensure_all_loaded_plugins_load_paths_are_added_to_the_load_path
+ only_load_the_following_plugins! [:stubby, :acts_as_chunky_bacon]
+
+ @initializer.add_plugin_load_paths
+
+ assert $LOAD_PATH.include?(File.join(plugin_fixture_path('default/stubby'), 'lib'))
+ assert $LOAD_PATH.include?(File.join(plugin_fixture_path('default/acts/acts_as_chunky_bacon'), 'lib'))
+ end
+
+ private
+
+ def load_plugins!
+ @initializer.add_plugin_load_paths
+ @initializer.load_plugins
+ end
+ end
+
+end
diff --git a/railties/test/plugin_loader_test.rb b/railties/test/plugin_loader_test.rb
index 3c2c37e794..fe77de4474 100644
--- a/railties/test/plugin_loader_test.rb
+++ b/railties/test/plugin_loader_test.rb
@@ -1,90 +1,140 @@
require File.dirname(__FILE__) + '/plugin_test_helper'
-class TestPluginLoader < Test::Unit::TestCase
- def setup
- @initializer = Rails::Initializer.new(Rails::Configuration.new)
- @valid_plugin_path = plugin_fixture_path('default/stubby')
- @empty_plugin_path = plugin_fixture_path('default/empty')
- end
-
- def test_determining_if_the_plugin_order_has_been_explicitly_set
- loader = loader_for(@valid_plugin_path)
- assert !loader.send(:explicit_plugin_loading_order?)
- only_load_the_following_plugins! %w(stubby acts_as_chunky_bacon)
- assert loader.send(:explicit_plugin_loading_order?)
- end
-
- def test_enabled_if_not_named_explicitly
- stubby_loader = loader_for(@valid_plugin_path)
- acts_as_loader = loader_for('acts_as/acts_as_chunky_bacon')
+uses_mocha "Plugin Loader Tests" do
+
+ class TestPluginLoader < Test::Unit::TestCase
+ ORIGINAL_LOAD_PATH = $LOAD_PATH.dup
- only_load_the_following_plugins! ['stubby', :all]
- assert stubby_loader.send(:enabled?)
- assert acts_as_loader.send(:enabled?)
+ def setup
+ reset_load_path!
+
+ @configuration = Rails::Configuration.new
+ @configuration.plugin_paths << plugin_fixture_root_path
+ @initializer = Rails::Initializer.new(@configuration)
+ @valid_plugin_path = plugin_fixture_path('default/stubby')
+ @empty_plugin_path = plugin_fixture_path('default/empty')
+
+ @loader = Rails::Plugin::Loader.new(@initializer)
+ end
+
+ def test_should_locate_plugins_by_asking_each_locator_specifed_in_configuration_for_its_plugins_result
+ locator_1 = stub(:plugins => [:a, :b, :c])
+ locator_2 = stub(:plugins => [:d, :e, :f])
+ locator_class_1 = stub(:new => locator_1)
+ locator_class_2 = stub(:new => locator_2)
+ @configuration.plugin_locators = [locator_class_1, locator_class_2]
+ assert_equal [:a, :b, :c, :d, :e, :f], @loader.send(:locate_plugins)
+ end
- assert stubby_loader.send(:explicitly_enabled?)
- assert !acts_as_loader.send(:explicitly_enabled?)
- end
-
- def test_determining_whether_a_given_plugin_is_loaded
- plugin_loader = loader_for(@valid_plugin_path)
- assert !plugin_loader.loaded?
- assert_nothing_raised do
- plugin_loader.send(:register_plugin_as_loaded)
- end
- assert plugin_loader.loaded?
- end
-
- def test_if_a_path_is_a_plugin_path
- # This is a plugin path, with a lib dir
- assert loader_for(@valid_plugin_path).plugin_path?
- # This just has an init.rb and no lib dir
- assert loader_for(plugin_fixture_path('default/plugin_with_no_lib_dir')).plugin_path?
- # This would be a plugin path, but the directory is empty
- assert !loader_for(plugin_fixture_path('default/empty')).plugin_path?
- # This is a non sense path
- assert !loader_for(plugin_fixture_path('default/this_directory_does_not_exist')).plugin_path?
- end
-
- def test_if_you_try_to_load_a_non_plugin_path_you_get_a_load_error
- # This path is fine so nothing is raised
- assert_nothing_raised do
- loader_for(@valid_plugin_path).send(:report_nonexistant_or_empty_plugin!)
+ def test_should_memoize_the_result_of_locate_plugins_as_all_plugins
+ plugin_list = [:a, :b, :c]
+ @loader.expects(:locate_plugins).once.returns(plugin_list)
+ assert_equal plugin_list, @loader.all_plugins
+ assert_equal plugin_list, @loader.all_plugins # ensuring that locate_plugins isn't called again
end
- # This is an empty path so it raises
- assert_raises(LoadError) do
- loader_for(@empty_plugin_path).send(:report_nonexistant_or_empty_plugin!)
+ def test_should_return_empty_array_if_configuration_plugins_is_empty
+ @configuration.plugins = []
+ assert_equal [], @loader.plugins
end
- assert_raises(LoadError) do
- loader_for('this_is_not_a_plugin_directory').send(:report_nonexistant_or_empty_plugin!)
+ def test_should_find_all_availble_plugins_and_return_as_all_plugins
+ failure_tip = "It's likely someone has added a new plugin fixture without updating this list"
+ assert_plugins [:a, :acts_as_chunky_bacon, :plugin_with_no_lib_dir, :stubby], @loader.all_plugins, failure_tip
end
- end
-
- def test_loading_a_plugin_gives_the_init_file_access_to_all_it_needs
- failure_tip = "Perhaps someone has written another test that loads this same plugin and therefore makes the StubbyMixin constant defined already."
- assert !defined?(StubbyMixin), failure_tip
- assert !added_to_load_path?(@valid_plugin_path)
- # The init.rb of this plugin raises if it doesn't have access to all the things it needs
- assert_nothing_raised do
- loader_for(@valid_plugin_path).load
- end
- assert added_to_load_path?(@valid_plugin_path)
- assert defined?(StubbyMixin)
- end
-
- private
- def loader_for(path, initializer = @initializer)
- Rails::Plugin::Loader.new(initializer, path)
+
+ def test_should_return_all_plugins_as_plugins_when_registered_plugin_list_is_untouched
+ failure_tip = "It's likely someone has added a new plugin fixture without updating this list"
+ assert_plugins [:a, :acts_as_chunky_bacon, :plugin_with_no_lib_dir, :stubby], @loader.plugins, failure_tip
+ end
+
+ def test_should_return_all_plugins_as_plugins_when_registered_plugin_list_is_nil
+ @configuration.plugins = nil
+ failure_tip = "It's likely someone has added a new plugin fixture without updating this list"
+ assert_plugins [:a, :acts_as_chunky_bacon, :plugin_with_no_lib_dir, :stubby], @loader.plugins, failure_tip
+ end
+
+ def test_should_return_specific_plugins_named_in_config_plugins_array_if_set
+ plugin_names = [:acts_as_chunky_bacon, :stubby]
+ only_load_the_following_plugins! plugin_names
+ assert_plugins plugin_names, @loader.plugins
+ end
+
+ def test_should_respect_the_order_of_plugins_given_in_configuration
+ plugin_names = [:stubby, :acts_as_chunky_bacon]
+ only_load_the_following_plugins! plugin_names
+ assert_plugins plugin_names, @loader.plugins
+ end
+
+ def test_should_load_all_plugins_in_natural_order_when_all_is_used
+ only_load_the_following_plugins! [:all]
+ failure_tip = "It's likely someone has added a new plugin fixture without updating this list"
+ assert_plugins [:a, :acts_as_chunky_bacon, :plugin_with_no_lib_dir, :stubby], @loader.plugins, failure_tip
end
- def plugin_fixture_path(path)
- File.join(plugin_fixture_root_path, path)
+ def test_should_load_specified_plugins_in_order_and_then_all_remaining_plugins_when_all_is_used
+ only_load_the_following_plugins! [:stubby, :acts_as_chunky_bacon, :all]
+ failure_tip = "It's likely someone has added a new plugin fixture without updating this list"
+ assert_plugins [:stubby, :acts_as_chunky_bacon, :a, :plugin_with_no_lib_dir], @loader.plugins, failure_tip
end
- def added_to_load_path?(path)
- $LOAD_PATH.grep(/#{path}/).size == 1
+ def test_should_be_able_to_specify_loading_of_plugins_loaded_after_all
+ only_load_the_following_plugins! [:stubby, :all, :acts_as_chunky_bacon]
+ failure_tip = "It's likely someone has added a new plugin fixture without updating this list"
+ assert_plugins [:stubby, :a, :plugin_with_no_lib_dir, :acts_as_chunky_bacon], @loader.plugins, failure_tip
+ end
+
+ def test_should_accept_plugin_names_given_as_strings
+ only_load_the_following_plugins! ['stubby', 'acts_as_chunky_bacon', :a, :plugin_with_no_lib_dir]
+ failure_tip = "It's likely someone has added a new plugin fixture without updating this list"
+ assert_plugins [:stubby, :acts_as_chunky_bacon, :a, :plugin_with_no_lib_dir], @loader.plugins, failure_tip
+ end
+
+ def test_should_add_plugin_load_paths_to_global_LOAD_PATH_array
+ only_load_the_following_plugins! [:stubby, :acts_as_chunky_bacon]
+ stubbed_application_lib_index_in_LOAD_PATHS = 5
+ @loader.stubs(:application_lib_index).returns(stubbed_application_lib_index_in_LOAD_PATHS)
+
+ @loader.add_plugin_load_paths
+
+ assert $LOAD_PATH.index(File.join(plugin_fixture_path('default/stubby'), 'lib')) >= stubbed_application_lib_index_in_LOAD_PATHS
+ assert $LOAD_PATH.index(File.join(plugin_fixture_path('default/acts/acts_as_chunky_bacon'), 'lib')) >= stubbed_application_lib_index_in_LOAD_PATHS
+ end
+
+ def test_should_add_plugin_load_paths_to_Dependencies_load_paths
+ only_load_the_following_plugins! [:stubby, :acts_as_chunky_bacon]
+
+ @loader.add_plugin_load_paths
+
+ assert Dependencies.load_paths.include?(File.join(plugin_fixture_path('default/stubby'), 'lib'))
+ assert Dependencies.load_paths.include?(File.join(plugin_fixture_path('default/acts/acts_as_chunky_bacon'), 'lib'))
end
+ def test_should_add_plugin_load_paths_to_Dependencies_load_once_paths
+ only_load_the_following_plugins! [:stubby, :acts_as_chunky_bacon]
+
+ @loader.add_plugin_load_paths
+
+ assert Dependencies.load_once_paths.include?(File.join(plugin_fixture_path('default/stubby'), 'lib'))
+ assert Dependencies.load_once_paths.include?(File.join(plugin_fixture_path('default/acts/acts_as_chunky_bacon'), 'lib'))
+ end
+
+ def test_should_add_all_load_paths_from_a_plugin_to_LOAD_PATH_array
+ plugin_load_paths = ["a", "b"]
+ plugin = stub(:load_paths => plugin_load_paths)
+ @loader.stubs(:plugins).returns([plugin])
+
+ @loader.add_plugin_load_paths
+
+ plugin_load_paths.each { |path| assert $LOAD_PATH.include?(path) }
+ end
+
+ private
+
+ def reset_load_path!
+ $LOAD_PATH.clear
+ ORIGINAL_LOAD_PATH.each { |path| $LOAD_PATH << path }
+ end
+ end
+
end \ No newline at end of file
diff --git a/railties/test/plugin_locator_test.rb b/railties/test/plugin_locator_test.rb
index 3ac4493d47..ccd270dd10 100644
--- a/railties/test/plugin_locator_test.rb
+++ b/railties/test/plugin_locator_test.rb
@@ -1,61 +1,69 @@
require File.dirname(__FILE__) + '/plugin_test_helper'
-class TestPluginFileSystemLocator < Test::Unit::TestCase
- def setup
- configuration = Rails::Configuration.new
- # We need to add our testing plugin directory to the plugin paths so
- # the locator knows where to look for our plugins
- configuration.plugin_paths << plugin_fixture_root_path
- @initializer = Rails::Initializer.new(configuration)
- @locator = new_locator
- end
+uses_mocha "Plugin Locator Tests" do
+
+ class PluginLocatorTest < Test::Unit::TestCase
- def test_no_plugins_are_loaded_if_the_configuration_has_an_empty_plugin_list
- only_load_the_following_plugins! []
- assert_equal [], @locator.plugins
- end
+ def test_should_require_subclasses_to_implement_the_plugins_method
+ assert_raises(RuntimeError) do
+ Rails::Plugin::Locator.new(nil).plugins
+ end
+ end
- def test_only_the_specified_plugins_are_located_in_the_order_listed
- plugin_names = [:stubby, :acts_as_chunky_bacon]
- only_load_the_following_plugins! plugin_names
- assert_equal plugin_names, @locator.plugin_names
- end
+ def test_should_iterator_over_plugins_returned_by_plugins_when_calling_each
+ locator = Rails::Plugin::Locator.new(nil)
+ locator.stubs(:plugins).returns([:a, :b, :c])
+ plugin_consumer = mock
+ plugin_consumer.expects(:consume).with(:a)
+ plugin_consumer.expects(:consume).with(:b)
+ plugin_consumer.expects(:consume).with(:c)
+
+ locator.each do |plugin|
+ plugin_consumer.consume(plugin)
+ end
+ end
- def test_all_plugins_are_loaded_when_registered_plugin_list_is_untouched
- failure_tip = "It's likely someone has added a new plugin fixture without updating this list"
- assert_equal [:a, :acts_as_chunky_bacon, :plugin_with_no_lib_dir, :stubby], @locator.plugin_names, failure_tip
end
+
+
+ class PluginFileSystemLocatorTest < Test::Unit::TestCase
+ def setup
+ @configuration = Rails::Configuration.new
+ # We need to add our testing plugin directory to the plugin paths so
+ # the locator knows where to look for our plugins
+ @configuration.plugin_paths << plugin_fixture_root_path
+ @initializer = Rails::Initializer.new(@configuration)
+ @locator = Rails::Plugin::FileSystemLocator.new(@initializer)
+ @valid_plugin_path = plugin_fixture_path('default/stubby')
+ @empty_plugin_path = plugin_fixture_path('default/empty')
+ end
+
+ def test_should_return_rails_plugin_instances_when_calling_create_plugin_with_a_valid_plugin_directory
+ assert_kind_of Rails::Plugin, @locator.send(:create_plugin, @valid_plugin_path)
+ end
- def test_all_plugins_loaded_when_all_is_used
- plugin_names = [:stubby, :acts_as_chunky_bacon, :all]
- only_load_the_following_plugins! plugin_names
- failure_tip = "It's likely someone has added a new plugin fixture without updating this list"
- assert_equal [:stubby, :acts_as_chunky_bacon, :a, :plugin_with_no_lib_dir], @locator.plugin_names, failure_tip
- end
+ def test_should_return_nil_when_calling_create_plugin_with_an_invalid_plugin_directory
+ assert_nil @locator.send(:create_plugin, @empty_plugin_path)
+ end
- def test_all_plugins_loaded_after_all
- plugin_names = [:stubby, :all, :acts_as_chunky_bacon]
- only_load_the_following_plugins! plugin_names
- failure_tip = "It's likely someone has added a new plugin fixture without updating this list"
- assert_equal [:stubby, :a, :plugin_with_no_lib_dir, :acts_as_chunky_bacon], @locator.plugin_names, failure_tip
- end
+ def test_should_return_all_plugins_found_under_the_set_plugin_paths
+ assert_equal ["a", "acts_as_chunky_bacon", "plugin_with_no_lib_dir", "stubby"], @locator.plugins.map(&:name)
+ end
- def test_plugin_names_may_be_strings
- plugin_names = ['stubby', 'acts_as_chunky_bacon', :a, :plugin_with_no_lib_dir]
- only_load_the_following_plugins! plugin_names
- failure_tip = "It's likely someone has added a new plugin fixture without updating this list"
- assert_equal [:stubby, :acts_as_chunky_bacon, :a, :plugin_with_no_lib_dir], @locator.plugin_names, failure_tip
- end
+ def test_should_find_plugins_only_under_the_plugin_paths_set_in_configuration
+ @configuration.plugin_paths = [File.join(plugin_fixture_root_path, "default")]
+ assert_equal ["acts_as_chunky_bacon", "plugin_with_no_lib_dir", "stubby"], @locator.plugins.map(&:name)
+
+ @configuration.plugin_paths = [File.join(plugin_fixture_root_path, "alternate")]
+ assert_equal ["a"], @locator.plugins.map(&:name)
+ end
- def test_registering_a_plugin_name_that_does_not_exist_raises_a_load_error
- only_load_the_following_plugins! [:stubby, :acts_as_a_non_existant_plugin]
- assert_raises(LoadError) do
- @initializer.load_plugins
+ def test_should_not_raise_any_error_and_return_no_plugins_if_the_plugin_path_value_does_not_exist
+ @configuration.plugin_paths = ["some_missing_directory"]
+ assert_nothing_raised do
+ assert @locator.plugins.empty?
+ end
end
end
-
- private
- def new_locator(initializer = @initializer)
- Rails::Plugin::FileSystemLocator.new(initializer)
- end
-end \ No newline at end of file
+
+end # uses_mocha \ No newline at end of file
diff --git a/railties/test/plugin_test.rb b/railties/test/plugin_test.rb
new file mode 100644
index 0000000000..a791e42ae8
--- /dev/null
+++ b/railties/test/plugin_test.rb
@@ -0,0 +1,130 @@
+require File.dirname(__FILE__) + '/plugin_test_helper'
+
+uses_mocha "Plugin Tests" do
+
+ class PluginTest < Test::Unit::TestCase
+
+ def setup
+ @initializer = Rails::Initializer.new(Rails::Configuration.new)
+ @valid_plugin_path = plugin_fixture_path('default/stubby')
+ @empty_plugin_path = plugin_fixture_path('default/empty')
+ end
+
+ def test_should_determine_plugin_name_from_the_directory_of_the_plugin
+ assert_equal 'stubby', plugin_for(@valid_plugin_path).name
+ assert_equal 'empty', plugin_for(@empty_plugin_path).name
+ end
+
+ def test_should_not_be_loaded_when_created
+ assert !plugin_for(@valid_plugin_path).loaded?
+ end
+
+ def test_should_be_marked_as_loaded_when_load_is_called
+ plugin = plugin_for(@valid_plugin_path)
+ assert !plugin.loaded?
+ plugin.stubs(:evaluate_init_rb)
+ assert_nothing_raised do
+ plugin.send(:load, anything)
+ end
+ assert plugin.loaded?
+ end
+
+ def test_should_determine_validity_of_given_path
+ # This is a plugin path, with a lib dir
+ assert plugin_for(@valid_plugin_path).valid?
+ # This just has an init.rb and no lib dir
+ assert plugin_for(plugin_fixture_path('default/plugin_with_no_lib_dir')).valid?
+ # This would be a plugin path, but the directory is empty
+ assert !plugin_for(plugin_fixture_path('default/empty')).valid?
+ # This is a non sense path
+ assert !plugin_for(plugin_fixture_path('default/this_directory_does_not_exist')).valid?
+ end
+
+ def test_should_return_empty_array_for_load_paths_when_plugin_has_no_lib_directory
+ assert_equal [], plugin_for(plugin_fixture_path('default/plugin_with_no_lib_dir')).load_paths
+ end
+
+ def test_should_return_array_with_lib_path_for_load_paths_when_plugin_has_a_lib_directory
+ expected_lib_dir = File.join(plugin_fixture_path('default/stubby'), 'lib')
+ assert_equal [expected_lib_dir], plugin_for(plugin_fixture_path('default/stubby')).load_paths
+ end
+
+ def test_should_raise_a_load_error_when_trying_to_determine_the_load_paths_from_an_invalid_plugin
+ assert_nothing_raised do
+ plugin_for(@valid_plugin_path).load_paths
+ end
+
+ assert_raises(LoadError) do
+ plugin_for(@empty_plugin_path).load_paths
+ end
+
+ assert_raises(LoadError) do
+ plugin_for('this_is_not_a_plugin_directory').load_paths
+ end
+ end
+
+ def test_should_raise_a_load_error_when_trying_to_load_an_invalid_plugin
+ # This path is fine so nothing is raised
+ assert_nothing_raised do
+ plugin = plugin_for(@valid_plugin_path)
+ plugin.stubs(:evaluate_init_rb)
+ plugin.send(:load, @initializer)
+ end
+
+ # This is an empty path so it raises
+ assert_raises(LoadError) do
+ plugin = plugin_for(@empty_plugin_path)
+ plugin.stubs(:evaluate_init_rb)
+ plugin.send(:load, @initializer)
+ end
+
+ assert_raises(LoadError) do
+ plugin = plugin_for('this_is_not_a_plugin_directory')
+ plugin.stubs(:evaluate_init_rb)
+ plugin.send(:load, @initializer)
+ end
+ end
+
+ def test_should_raise_a_load_error_when_trying_to_access_load_paths_of_an_invalid_plugin
+ # This path is fine so nothing is raised
+ assert_nothing_raised do
+ plugin_for(@valid_plugin_path).load_paths
+ end
+
+ # This is an empty path so it raises
+ assert_raises(LoadError) do
+ plugin_for(@empty_plugin_path).load_paths
+ end
+
+ assert_raises(LoadError) do
+ plugin_for('this_is_not_a_plugin_directory').load_paths
+ end
+ end
+
+ def test_loading_a_plugin_gives_the_init_file_access_to_all_it_needs
+ failure_tip = "Perhaps someone has written another test that loads this same plugin and therefore makes the StubbyMixin constant defined already."
+ assert !defined?(StubbyMixin), failure_tip
+ plugin = plugin_for(@valid_plugin_path)
+ plugin.load_paths.each { |path| $LOAD_PATH.unshift(path) }
+ # The init.rb of this plugin raises if it doesn't have access to all the things it needs
+ assert_nothing_raised do
+ plugin.load(@initializer)
+ end
+ assert defined?(StubbyMixin)
+ end
+
+ def test_should_sort_naturally_by_name
+ a = plugin_for("path/a")
+ b = plugin_for("path/b")
+ z = plugin_for("path/z")
+ assert_equal [a, b, z], [b, z, a].sort
+ end
+
+ private
+
+ def plugin_for(path)
+ Rails::Plugin.new(path)
+ end
+ end
+
+end # uses_mocha \ No newline at end of file
diff --git a/railties/test/plugin_test_helper.rb b/railties/test/plugin_test_helper.rb
index 0b065a5444..f8c094d19f 100644
--- a/railties/test/plugin_test_helper.rb
+++ b/railties/test/plugin_test_helper.rb
@@ -4,15 +4,26 @@ $:.unshift File.dirname(__FILE__) + "/../../activesupport/lib"
require 'test/unit'
require 'active_support'
require 'initializer'
+require File.join(File.dirname(__FILE__), 'abstract_unit')
# We need to set RAILS_ROOT if it isn't already set
RAILS_ROOT = '.' unless defined?(RAILS_ROOT)
+
class Test::Unit::TestCase
- def plugin_fixture_root_path
- File.join(File.dirname(__FILE__), 'fixtures', 'plugins')
- end
+ private
+ def plugin_fixture_root_path
+ File.join(File.dirname(__FILE__), 'fixtures', 'plugins')
+ end
+
+ def only_load_the_following_plugins!(plugins)
+ @initializer.configuration.plugins = plugins
+ end
- def only_load_the_following_plugins!(plugins)
- @initializer.configuration.plugins = plugins
- end
+ def plugin_fixture_path(path)
+ File.join(plugin_fixture_root_path, path)
+ end
+
+ def assert_plugins(list_of_names, array_of_plugins, message=nil)
+ assert_equal list_of_names.map(&:to_s), array_of_plugins.map(&:name), message
+ end
end \ No newline at end of file