diff options
Diffstat (limited to 'activesupport/test/dependencies_test.rb')
-rw-r--r-- | activesupport/test/dependencies_test.rb | 1207 |
1 files changed, 1207 insertions, 0 deletions
diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb new file mode 100644 index 0000000000..b1b3070891 --- /dev/null +++ b/activesupport/test/dependencies_test.rb @@ -0,0 +1,1207 @@ +# frozen_string_literal: true + +require "abstract_unit" +require "pp" +require "active_support/dependencies" +require "dependencies_test_helpers" + +module ModuleWithMissing + mattr_accessor :missing_count + def self.const_missing(name) + self.missing_count += 1 + name + end +end + +module ModuleWithConstant + InheritedConstant = "Hello" +end + +class DependenciesTest < ActiveSupport::TestCase + include DependenciesTestHelpers + + setup do + @loaded_features_copy = $LOADED_FEATURES.dup + end + + teardown do + ActiveSupport::Dependencies.clear + $LOADED_FEATURES.replace(@loaded_features_copy) + end + + def test_depend_on_path + expected = assert_raises(LoadError) do + Kernel.require "omgwtfbbq" + end + + e = assert_raises(LoadError) do + ActiveSupport::Dependencies.depend_on "omgwtfbbq" + end + assert_equal expected.path, e.path + end + + def test_require_dependency_accepts_an_object_which_implements_to_path + o = Object.new + def o.to_path; "dependencies/service_one"; end + assert_nothing_raised { + require_dependency o + } + assert defined?(ServiceOne) + ensure + remove_constants(:ServiceOne) + end + + def test_tracking_loaded_files + with_loading do + require_dependency "dependencies/service_one" + require_dependency "dependencies/service_two" + assert_equal 2, ActiveSupport::Dependencies.loaded.size + end + ensure + remove_constants(:ServiceOne, :ServiceTwo) + end + + def test_tracking_identical_loaded_files + with_loading do + require_dependency "dependencies/service_one" + require_dependency "dependencies/service_one" + assert_equal 1, ActiveSupport::Dependencies.loaded.size + end + ensure + remove_constants(:ServiceOne) + end + + def test_missing_dependency_raises_missing_source_file + assert_raise(LoadError) { require_dependency("missing_service") } + end + + def test_dependency_which_raises_exception_isnt_added_to_loaded_set + with_loading do + filename = "dependencies/raises_exception" + expanded = File.expand_path(filename) + $raises_exception_load_count = 0 + + 5.times do |count| + e = assert_raise Exception, "should have loaded dependencies/raises_exception which raises an exception" do + require_dependency filename + end + + assert_equal "Loading me failed, so do not add to loaded or history.", e.message + assert_equal count + 1, $raises_exception_load_count + + assert_not ActiveSupport::Dependencies.loaded.include?(expanded) + assert_not ActiveSupport::Dependencies.history.include?(expanded) + end + end + end + + def test_dependency_which_raises_doesnt_blindly_call_blame_file! + with_loading do + filename = "dependencies/raises_exception_without_blame_file" + assert_raises(Exception) { require_dependency filename } + end + end + + def test_warnings_should_be_enabled_on_first_load + with_loading "dependencies" do + old_warnings, ActiveSupport::Dependencies.warnings_on_first_load = ActiveSupport::Dependencies.warnings_on_first_load, true + filename = "check_warnings" + expanded = File.expand_path("dependencies/#{filename}", __dir__) + $check_warnings_load_count = 0 + + assert_not ActiveSupport::Dependencies.loaded.include?(expanded) + assert_not ActiveSupport::Dependencies.history.include?(expanded) + + silence_warnings { require_dependency filename } + assert_equal 1, $check_warnings_load_count + assert_equal true, $checked_verbose, "On first load warnings should be enabled." + + assert_includes ActiveSupport::Dependencies.loaded, expanded + ActiveSupport::Dependencies.clear + assert_not ActiveSupport::Dependencies.loaded.include?(expanded) + assert_includes ActiveSupport::Dependencies.history, expanded + + silence_warnings { require_dependency filename } + assert_equal 2, $check_warnings_load_count + assert_nil $checked_verbose, "After first load warnings should be left alone." + + assert_includes ActiveSupport::Dependencies.loaded, expanded + ActiveSupport::Dependencies.clear + assert_not ActiveSupport::Dependencies.loaded.include?(expanded) + assert_includes ActiveSupport::Dependencies.history, expanded + + enable_warnings { require_dependency filename } + assert_equal 3, $check_warnings_load_count + assert_equal true, $checked_verbose, "After first load warnings should be left alone." + + assert_includes ActiveSupport::Dependencies.loaded, expanded + ActiveSupport::Dependencies.warnings_on_first_load = old_warnings + end + end + + def test_mutual_dependencies_dont_infinite_loop + with_loading "dependencies" do + $mutual_dependencies_count = 0 + assert_nothing_raised { require_dependency "mutual_one" } + assert_equal 2, $mutual_dependencies_count + + ActiveSupport::Dependencies.clear + + $mutual_dependencies_count = 0 + assert_nothing_raised { require_dependency "mutual_two" } + assert_equal 2, $mutual_dependencies_count + end + end + + def test_circular_autoloading_detection + with_autoloading_fixtures do + e = assert_raise(RuntimeError) { Circular1 } + assert_equal "Circular dependency detected while autoloading constant Circular1", e.message + end + end + + def test_ensures_the_expected_constant_is_defined + with_autoloading_fixtures do + e = assert_raise(LoadError) { Typo } + assert_match %r{Unable to autoload constant Typo, expected .*/test/autoloading_fixtures/typo.rb to define it}, e.message + end + end + + def test_require_dependency_does_not_assume_any_particular_constant_is_defined + with_autoloading_fixtures do + require_dependency "typo" + assert_equal 1, TypO + end + end + + # Regression, see https://github.com/rails/rails/issues/16468. + def test_require_dependency_interaction_with_autoloading + with_autoloading_fixtures do + require_dependency "typo" + assert_equal 1, TypO + + e = assert_raise(LoadError) { Typo } + assert_match %r{Unable to autoload constant Typo, expected .*/test/autoloading_fixtures/typo.rb to define it}, e.message + end + end + + # Regression see https://github.com/rails/rails/issues/31694 + def test_included_constant_that_changes_to_have_exception_then_back_does_not_loop_forever + # This constant references a nested constant whose namespace will be auto-generated + parent_constant = <<-RUBY + class ConstantReloadError + AnotherConstant::ReloadError + end + RUBY + + # This constant's namespace will be auto-generated, + # also, we'll edit it to contain an error at load-time + child_constant = <<-RUBY + class AnotherConstant::ReloadError + # no_such_method_as_this + end + RUBY + + # Create a version which contains an error during loading + child_constant_with_error = child_constant.sub("# no_such_method_as_this", "no_such_method_as_this") + + fixtures_path = File.join(__dir__, "autoloading_fixtures") + Dir.mktmpdir(nil, fixtures_path) do |tmpdir| + # Set up the file structure where constants will be loaded from + child_constant_path = "#{tmpdir}/another_constant/reload_error.rb" + File.write("#{tmpdir}/constant_reload_error.rb", parent_constant) + Dir.mkdir("#{tmpdir}/another_constant") + File.write(child_constant_path, child_constant_with_error) + + tmpdir_name = tmpdir.split("/").last + with_loading("autoloading_fixtures/#{tmpdir_name}") do + # Load the file, with the error: + assert_raises(NameError) { + ConstantReloadError + } + + Timeout.timeout(0.1) do + # Remove the constant, as if Rails development middleware is reloading changed files: + ActiveSupport::Dependencies.remove_unloadable_constants! + assert_not defined?(AnotherConstant::ReloadError) + end + + # Change the file, so that it is **correct** this time: + File.write(child_constant_path, child_constant) + + # Again: Remove the constant, as if Rails development middleware is reloading changed files: + ActiveSupport::Dependencies.remove_unloadable_constants! + assert_not defined?(AnotherConstant::ReloadError) + + # Now, reload the _fixed_ constant: + assert ConstantReloadError + assert AnotherConstant::ReloadError + end + end + end + + def test_module_loading + with_autoloading_fixtures do + assert_kind_of Module, A + assert_kind_of Class, A::B + assert_kind_of Class, A::C::D + assert_kind_of Class, A::C::EM::F + end + end + + def test_non_existing_const_raises_name_error + with_autoloading_fixtures do + assert_raise(NameError) { DoesNotExist } + assert_raise(NameError) { NoModule::DoesNotExist } + assert_raise(NameError) { A::DoesNotExist } + assert_raise(NameError) { A::B::DoesNotExist } + end + end + + def test_directories_manifest_as_modules_unless_const_defined + with_autoloading_fixtures do + assert_kind_of Module, ModuleFolder + end + ensure + remove_constants(:ModuleFolder) + end + + def test_module_with_nested_class + with_autoloading_fixtures do + assert_kind_of Class, ModuleFolder::NestedClass + end + ensure + remove_constants(:ModuleFolder) + end + + def test_module_with_nested_inline_class + with_autoloading_fixtures do + assert_kind_of Class, ModuleFolder::InlineClass + end + ensure + remove_constants(:ModuleFolder) + end + + def test_module_with_nested_class_requiring_lib_class + with_autoloading_fixtures do + _ = ModuleFolder::NestedWithRequire # assignment to silence parse-time warning "possibly useless use of :: in void context" + + assert defined?(ModuleFolder::LibClass) + assert_not ActiveSupport::Dependencies.autoloaded_constants.include?("ModuleFolder::LibClass") + assert_not ActiveSupport::Dependencies.autoloaded_constants.include?("ConstFromLib") + end + ensure + remove_constants(:ModuleFolder) + remove_constants(:ConstFromLib) + end + + def test_module_with_nested_class_and_parent_requiring_lib_class + with_autoloading_fixtures do + _ = NestedWithRequireParent # assignment to silence parse-time warning "possibly useless use of a constant in void context" + + assert defined?(ModuleFolder::LibClass) + assert_not ActiveSupport::Dependencies.autoloaded_constants.include?("ModuleFolder::LibClass") + assert_not ActiveSupport::Dependencies.autoloaded_constants.include?("ConstFromLib") + end + ensure + remove_constants(:ModuleFolder) + remove_constants(:ConstFromLib) + end + + def test_directories_may_manifest_as_nested_classes + with_autoloading_fixtures do + assert_kind_of Class, ClassFolder + end + ensure + remove_constants(:ClassFolder) + end + + def test_class_with_nested_class + with_autoloading_fixtures do + assert_kind_of Class, ClassFolder::NestedClass + end + ensure + remove_constants(:ClassFolder) + end + + def test_class_with_nested_inline_class + with_autoloading_fixtures do + assert_kind_of Class, ClassFolder::InlineClass + end + ensure + remove_constants(:ClassFolder) + end + + def test_class_with_nested_inline_subclass_of_parent + with_autoloading_fixtures do + assert_kind_of Class, ClassFolder::ClassFolderSubclass + assert_kind_of Class, ClassFolder + assert_equal "indeed", ClassFolder::ClassFolderSubclass::ConstantInClassFolder + end + ensure + remove_constants(:ClassFolder) + end + + def test_nested_class_can_access_sibling + with_autoloading_fixtures do + sibling = ModuleFolder::NestedClass.class_eval "NestedSibling" + assert defined?(ModuleFolder::NestedSibling) + assert_equal ModuleFolder::NestedSibling, sibling + end + ensure + remove_constants(:ModuleFolder) + end + + def test_raising_discards_autoloaded_constants + with_autoloading_fixtures do + e = assert_raises(Exception) { RaisesArbitraryException } + assert_equal("arbitrary exception message", e.message) + assert_not defined?(A) + assert_not defined?(RaisesArbitraryException) + end + ensure + remove_constants(:A, :RaisesArbitraryException) + end + + def test_throwing_discards_autoloaded_constants + with_autoloading_fixtures do + catch :t do + Throws + end + assert_not defined?(A) + assert_not defined?(Throws) + end + ensure + remove_constants(:A, :Throws) + end + + def test_doesnt_break_normal_require + path = File.expand_path("autoloading_fixtures/load_path", __dir__) + original_path = $:.dup + $:.push(path) + with_autoloading_fixtures do + # The _ = assignments are to prevent warnings + _ = RequiresConstant + assert defined?(RequiresConstant) + assert defined?(LoadedConstant) + ActiveSupport::Dependencies.clear + _ = RequiresConstant + assert defined?(RequiresConstant) + assert defined?(LoadedConstant) + end + ensure + remove_constants(:RequiresConstant, :LoadedConstant) + $:.replace(original_path) + end + + def test_doesnt_break_normal_require_nested + path = File.expand_path("autoloading_fixtures/load_path", __dir__) + original_path = $:.dup + $:.push(path) + + with_autoloading_fixtures do + # The _ = assignments are to prevent warnings + _ = LoadsConstant + assert defined?(LoadsConstant) + assert defined?(LoadedConstant) + ActiveSupport::Dependencies.clear + _ = LoadsConstant + assert defined?(LoadsConstant) + assert defined?(LoadedConstant) + end + ensure + remove_constants(:RequiresConstant, :LoadedConstant, :LoadsConstant) + $:.replace(original_path) + end + + def test_require_returns_true_when_file_not_yet_required + path = File.expand_path("autoloading_fixtures/load_path", __dir__) + original_path = $:.dup + $:.push(path) + + with_loading do + assert_equal true, require("loaded_constant") + end + ensure + remove_constants(:LoadedConstant) + $:.replace(original_path) + end + + def test_require_returns_true_when_file_not_yet_required_even_when_no_new_constants_added + path = File.expand_path("autoloading_fixtures/load_path", __dir__) + original_path = $:.dup + $:.push(path) + + with_loading do + Object.module_eval "module LoadedConstant; end" + assert_equal true, require("loaded_constant") + end + ensure + remove_constants(:LoadedConstant) + $:.replace(original_path) + end + + def test_require_returns_false_when_file_already_required + path = File.expand_path("autoloading_fixtures/load_path", __dir__) + original_path = $:.dup + $:.push(path) + + with_loading do + require "loaded_constant" + assert_equal false, require("loaded_constant") + end + ensure + remove_constants(:LoadedConstant) + $:.replace(original_path) + end + + def test_require_raises_load_error_when_file_not_found + with_loading do + assert_raise(LoadError) { require "this_file_dont_exist_dude" } + end + end + + def test_load_returns_true_when_file_found + path = File.expand_path("autoloading_fixtures/load_path", __dir__) + original_path = $:.dup + $:.push(path) + + with_loading do + assert_equal true, load("loaded_constant.rb") + assert_equal true, load("loaded_constant.rb") + end + ensure + remove_constants(:LoadedConstant) + $:.replace(original_path) + end + + def test_load_raises_load_error_when_file_not_found + with_loading do + assert_raise(LoadError) { load "this_file_dont_exist_dude.rb" } + end + end + + # This raises only on 2.5.. (warns on ..2.4) + if RUBY_VERSION > "2.5" + def test_access_thru_and_upwards_fails + with_autoloading_fixtures do + assert_not defined?(ModuleFolder) + assert_raise(NameError) { ModuleFolder::Object } + assert_raise(NameError) { ModuleFolder::NestedClass::Object } + end + ensure + remove_constants(:ModuleFolder) + end + end + + def test_non_existing_const_raises_name_error_with_fully_qualified_name + with_autoloading_fixtures do + e = assert_raise(NameError) { A::DoesNotExist.nil? } + assert_equal "uninitialized constant A::DoesNotExist", e.message + assert_equal :DoesNotExist, e.name + + e = assert_raise(NameError) { A::B::DoesNotExist.nil? } + assert_equal "uninitialized constant A::B::DoesNotExist", e.message + assert_equal :DoesNotExist, e.name + end + ensure + remove_constants(:A) + end + + def test_smart_name_error_strings + e = assert_raise NameError do + Object.module_eval "ImaginaryObject" + end + assert_includes "uninitialized constant ImaginaryObject", e.message + end + + def test_loadable_constants_for_path_should_handle_empty_autoloads + assert_equal [], ActiveSupport::Dependencies.loadable_constants_for_path("hello") + end + + def test_loadable_constants_for_path_should_handle_relative_paths + fake_root = "dependencies" + relative_root = File.expand_path("dependencies", __dir__) + ["", "/"].each do |suffix| + with_loading fake_root + suffix do + assert_equal ["A::B"], ActiveSupport::Dependencies.loadable_constants_for_path(relative_root + "/a/b") + end + end + end + + def test_loadable_constants_for_path_should_provide_all_results + fake_root = "/usr/apps/backpack" + with_loading fake_root, fake_root + "/lib" do + root = ActiveSupport::Dependencies.autoload_paths.first + assert_equal ["Lib::A::B", "A::B"], ActiveSupport::Dependencies.loadable_constants_for_path(root + "/lib/a/b") + end + end + + def test_loadable_constants_for_path_should_uniq_results + fake_root = "/usr/apps/backpack/lib" + with_loading fake_root, fake_root + "/" do + root = ActiveSupport::Dependencies.autoload_paths.first + assert_equal ["A::B"], ActiveSupport::Dependencies.loadable_constants_for_path(root + "/a/b") + end + end + + def test_loadable_constants_with_load_path_without_trailing_slash + path = File.expand_path("autoloading_fixtures/class_folder/inline_class.rb", __dir__) + with_loading "autoloading_fixtures/class/" do + assert_equal [], ActiveSupport::Dependencies.loadable_constants_for_path(path) + end + end + + def test_qualified_const_defined + assert ActiveSupport::Dependencies.qualified_const_defined?("Object") + assert ActiveSupport::Dependencies.qualified_const_defined?("::Object") + assert ActiveSupport::Dependencies.qualified_const_defined?("::Object::Kernel") + assert ActiveSupport::Dependencies.qualified_const_defined?("::ActiveSupport::TestCase") + end + + def test_qualified_const_defined_should_not_call_const_missing + ModuleWithMissing.missing_count = 0 + assert_not ActiveSupport::Dependencies.qualified_const_defined?("ModuleWithMissing::A") + assert_equal 0, ModuleWithMissing.missing_count + assert_not ActiveSupport::Dependencies.qualified_const_defined?("ModuleWithMissing::A::B") + assert_equal 0, ModuleWithMissing.missing_count + end + + def test_qualified_const_defined_explodes_with_invalid_const_name + assert_raises(NameError) { ActiveSupport::Dependencies.qualified_const_defined?("invalid") } + end + + def test_autoloaded? + with_autoloading_fixtures do + assert_not ActiveSupport::Dependencies.autoloaded?("ModuleFolder") + assert_not ActiveSupport::Dependencies.autoloaded?("ModuleFolder::NestedClass") + + assert ActiveSupport::Dependencies.autoloaded?(ModuleFolder) + + assert ActiveSupport::Dependencies.autoloaded?("ModuleFolder") + assert_not ActiveSupport::Dependencies.autoloaded?("ModuleFolder::NestedClass") + + assert ActiveSupport::Dependencies.autoloaded?(ModuleFolder::NestedClass) + + assert ActiveSupport::Dependencies.autoloaded?("ModuleFolder") + assert ActiveSupport::Dependencies.autoloaded?("ModuleFolder::NestedClass") + + assert ActiveSupport::Dependencies.autoloaded?("::ModuleFolder") + assert ActiveSupport::Dependencies.autoloaded?(:ModuleFolder) + + # Anonymous modules aren't autoloaded. + assert_not ActiveSupport::Dependencies.autoloaded?(Module.new) + + nil_name = Module.new + def nil_name.name() nil end + assert_not ActiveSupport::Dependencies.autoloaded?(nil_name) + end + ensure + remove_constants(:ModuleFolder) + end + + def test_qualified_name_for + assert_equal "A", ActiveSupport::Dependencies.qualified_name_for(Object, :A) + assert_equal "A", ActiveSupport::Dependencies.qualified_name_for(:Object, :A) + assert_equal "A", ActiveSupport::Dependencies.qualified_name_for("Object", :A) + assert_equal "A", ActiveSupport::Dependencies.qualified_name_for("::Object", :A) + + assert_equal "ActiveSupport::Dependencies::A", ActiveSupport::Dependencies.qualified_name_for(:'ActiveSupport::Dependencies', :A) + assert_equal "ActiveSupport::Dependencies::A", ActiveSupport::Dependencies.qualified_name_for(ActiveSupport::Dependencies, :A) + end + + def test_file_search + with_loading "dependencies" do + root = ActiveSupport::Dependencies.autoload_paths.first + assert_nil ActiveSupport::Dependencies.search_for_file("service_three") + assert_nil ActiveSupport::Dependencies.search_for_file("service_three.rb") + assert_equal root + "/service_one.rb", ActiveSupport::Dependencies.search_for_file("service_one") + assert_equal root + "/service_one.rb", ActiveSupport::Dependencies.search_for_file("service_one.rb") + end + end + + def test_file_search_uses_first_in_load_path + with_loading "dependencies", "autoloading_fixtures" do + deps, autoload = ActiveSupport::Dependencies.autoload_paths + assert_match %r/dependencies/, deps + assert_match %r/autoloading_fixtures/, autoload + + assert_equal deps + "/conflict.rb", ActiveSupport::Dependencies.search_for_file("conflict") + end + with_loading "autoloading_fixtures", "dependencies" do + autoload, deps = ActiveSupport::Dependencies.autoload_paths + assert_match %r/dependencies/, deps + assert_match %r/autoloading_fixtures/, autoload + + assert_equal autoload + "/conflict.rb", ActiveSupport::Dependencies.search_for_file("conflict") + end + end + + def test_custom_const_missing_should_work + Object.module_eval <<-end_eval, __FILE__, __LINE__ + 1 + module ModuleWithCustomConstMissing + def self.const_missing(name) + const_set name, name.to_s.hash + end + + module A + end + end + end_eval + + with_autoloading_fixtures do + assert_kind_of Integer, ::ModuleWithCustomConstMissing::B + assert_kind_of Module, ::ModuleWithCustomConstMissing::A + assert_kind_of String, ::ModuleWithCustomConstMissing::A::B + end + ensure + remove_constants(:ModuleWithCustomConstMissing) + end + + def test_const_missing_in_anonymous_modules_loads_top_level_constants + with_autoloading_fixtures do + # class_eval STRING pushes the class to the nesting of the eval'ed code. + klass = Class.new.class_eval "EM" + assert_equal EM, klass + end + ensure + remove_constants(:EM) + end + + def test_const_missing_in_anonymous_modules_raises_if_the_constant_belongs_to_Object + with_autoloading_fixtures do + require_dependency "em" + + mod = Module.new + e = assert_raise(NameError) { mod::EM } + assert_equal "EM cannot be autoloaded from an anonymous class or module", e.message + assert_equal :EM, e.name + end + ensure + remove_constants(:EM) + end + + def test_removal_from_tree_should_be_detected + with_loading "dependencies" do + c = ServiceOne + ActiveSupport::Dependencies.clear + assert_not defined?(ServiceOne) + e = assert_raise ArgumentError do + ActiveSupport::Dependencies.load_missing_constant(c, :FakeMissing) + end + assert_match %r{ServiceOne has been removed from the module tree}i, e.message + end + ensure + remove_constants(:ServiceOne) + end + + def test_references_should_work + with_loading "dependencies" do + c = ActiveSupport::Dependencies.reference("ServiceOne") + service_one_first = ServiceOne + assert_equal service_one_first, c.get("ServiceOne") + ActiveSupport::Dependencies.clear + assert_not defined?(ServiceOne) + service_one_second = ServiceOne + assert_not_equal service_one_first, c.get("ServiceOne") + assert_equal service_one_second, c.get("ServiceOne") + end + ensure + remove_constants(:ServiceOne) + end + + def test_constantize_shortcut_for_cached_constant_lookups + with_loading "dependencies" do + assert_equal ServiceOne, ActiveSupport::Dependencies.constantize("ServiceOne") + end + ensure + remove_constants(:ServiceOne) + end + + def test_nested_load_error_isnt_rescued + with_loading "dependencies" do + assert_raise(LoadError) do + RequiresNonexistent1 + end + end + end + + def test_autoload_once_paths_do_not_add_to_autoloaded_constants + old_path = ActiveSupport::Dependencies.autoload_once_paths + with_autoloading_fixtures do + ActiveSupport::Dependencies.autoload_once_paths = ActiveSupport::Dependencies.autoload_paths.dup + + assert_not ActiveSupport::Dependencies.autoloaded?("ModuleFolder") + assert_not ActiveSupport::Dependencies.autoloaded?("ModuleFolder::NestedClass") + assert_not ActiveSupport::Dependencies.autoloaded?(ModuleFolder) + + 1 if ModuleFolder::NestedClass # 1 if to avoid warning + assert_not ActiveSupport::Dependencies.autoloaded?(ModuleFolder::NestedClass) + end + ensure + remove_constants(:ModuleFolder) + ActiveSupport::Dependencies.autoload_once_paths = old_path + end + + def test_autoload_once_pathnames_do_not_add_to_autoloaded_constants + with_autoloading_fixtures do + pathnames = ActiveSupport::Dependencies.autoload_paths.collect { |p| Pathname.new(p) } + ActiveSupport::Dependencies.autoload_paths = pathnames + ActiveSupport::Dependencies.autoload_once_paths = pathnames + + assert_not ActiveSupport::Dependencies.autoloaded?("ModuleFolder") + assert_not ActiveSupport::Dependencies.autoloaded?("ModuleFolder::NestedClass") + assert_not ActiveSupport::Dependencies.autoloaded?(ModuleFolder) + + 1 if ModuleFolder::NestedClass # 1 if to avoid warning + assert_not ActiveSupport::Dependencies.autoloaded?(ModuleFolder::NestedClass) + end + ensure + remove_constants(:ModuleFolder) + ActiveSupport::Dependencies.autoload_once_paths = [] + end + + def test_application_should_special_case_application_controller + with_autoloading_fixtures do + require_dependency "application" + assert_equal 10, ApplicationController + assert ActiveSupport::Dependencies.autoloaded?(:ApplicationController) + end + ensure + remove_constants(:ApplicationController) + end + + def test_preexisting_constants_are_not_marked_as_autoloaded + with_autoloading_fixtures do + require_dependency "em" + assert ActiveSupport::Dependencies.autoloaded?(:EM) + ActiveSupport::Dependencies.clear + end + + Object.const_set :EM, Class.new + with_autoloading_fixtures do + require_dependency "em" + assert_not ActiveSupport::Dependencies.autoloaded?(:EM), "EM shouldn't be marked autoloaded!" + ActiveSupport::Dependencies.clear + end + ensure + remove_constants(:EM) + end + + def test_constants_in_capitalized_nesting_marked_as_autoloaded + with_autoloading_fixtures do + ActiveSupport::Dependencies.load_missing_constant(HTML, "SomeClass") + + assert ActiveSupport::Dependencies.autoloaded?("HTML::SomeClass") + end + ensure + remove_constants(:HTML) + end + + def test_unloadable + with_autoloading_fixtures do + Object.const_set :M, Module.new + M.unloadable + + ActiveSupport::Dependencies.clear + assert_not defined?(M) + + Object.const_set :M, Module.new + ActiveSupport::Dependencies.clear + assert_not defined?(M), "Dependencies should unload unloadable constants each time" + end + end + + def test_unloadable_should_fail_with_anonymous_modules + with_autoloading_fixtures do + m = Module.new + assert_raise(ArgumentError) { m.unloadable } + end + end + + def test_unloadable_should_return_change_flag + with_autoloading_fixtures do + Object.const_set :M, Module.new + assert_equal true, M.unloadable + assert_equal false, M.unloadable + end + ensure + remove_constants(:M) + end + + def test_unloadable_constants_should_receive_callback + Object.const_set :C, Class.new { def self.before_remove_const; end } + C.unloadable + assert_called(C, :before_remove_const, times: 1) do + assert_respond_to C, :before_remove_const + ActiveSupport::Dependencies.clear + assert_not defined?(C) + end + ensure + remove_constants(:C) + end + + def test_new_contants_in_without_constants + assert_equal [], (ActiveSupport::Dependencies.new_constants_in(Object) { }) + assert ActiveSupport::Dependencies.constant_watch_stack.all? { |k, v| v.empty? } + end + + def test_new_constants_in_with_a_single_constant + assert_equal ["Hello"], ActiveSupport::Dependencies.new_constants_in(Object) { + Object.const_set :Hello, 10 + }.map(&:to_s) + assert ActiveSupport::Dependencies.constant_watch_stack.all? { |k, v| v.empty? } + ensure + remove_constants(:Hello) + end + + def test_new_constants_in_with_nesting + outer = ActiveSupport::Dependencies.new_constants_in(Object) do + Object.const_set :OuterBefore, 10 + + assert_equal ["Inner"], ActiveSupport::Dependencies.new_constants_in(Object) { + Object.const_set :Inner, 20 + }.map(&:to_s) + + Object.const_set :OuterAfter, 30 + end + + assert_equal ["OuterAfter", "OuterBefore"], outer.sort.map(&:to_s) + assert ActiveSupport::Dependencies.constant_watch_stack.all? { |k, v| v.empty? } + ensure + remove_constants(:OuterBefore, :Inner, :OuterAfter) + end + + def test_new_constants_in_module + Object.const_set :M, Module.new + + outer = ActiveSupport::Dependencies.new_constants_in(M) do + M.const_set :OuterBefore, 10 + + inner = ActiveSupport::Dependencies.new_constants_in(M) do + M.const_set :Inner, 20 + end + assert_equal ["M::Inner"], inner + + M.const_set :OuterAfter, 30 + end + assert_equal ["M::OuterAfter", "M::OuterBefore"], outer.sort + assert ActiveSupport::Dependencies.constant_watch_stack.all? { |k, v| v.empty? } + ensure + remove_constants(:M) + end + + def test_new_constants_in_module_using_name + outer = ActiveSupport::Dependencies.new_constants_in(:M) do + Object.const_set :M, Module.new + M.const_set :OuterBefore, 10 + + inner = ActiveSupport::Dependencies.new_constants_in(:M) do + M.const_set :Inner, 20 + end + assert_equal ["M::Inner"], inner + + M.const_set :OuterAfter, 30 + end + assert_equal ["M::OuterAfter", "M::OuterBefore"], outer.sort + assert ActiveSupport::Dependencies.constant_watch_stack.all? { |k, v| v.empty? } + ensure + remove_constants(:M) + end + + def test_new_constants_in_with_inherited_constants + m = ActiveSupport::Dependencies.new_constants_in(:Object) do + Object.class_eval { include ModuleWithConstant } + end + assert_equal [], m + end + + def test_new_constants_in_with_illegal_module_name_raises_correct_error + assert_raise(NameError) do + ActiveSupport::Dependencies.new_constants_in("Illegal-Name") { } + end + end + + def test_file_with_multiple_constants_and_require_dependency + with_autoloading_fixtures do + assert_not defined?(MultipleConstantFile) + assert_not defined?(SiblingConstant) + + require_dependency "multiple_constant_file" + assert defined?(MultipleConstantFile) + assert defined?(SiblingConstant) + assert ActiveSupport::Dependencies.autoloaded?(:MultipleConstantFile) + assert ActiveSupport::Dependencies.autoloaded?(:SiblingConstant) + ActiveSupport::Dependencies.clear + + assert_not defined?(MultipleConstantFile) + assert_not defined?(SiblingConstant) + end + ensure + remove_constants(:MultipleConstantFile, :SiblingConstant) + end + + def test_file_with_multiple_constants_and_auto_loading + with_autoloading_fixtures do + assert_not defined?(MultipleConstantFile) + assert_not defined?(SiblingConstant) + + assert_equal 10, MultipleConstantFile + + assert defined?(MultipleConstantFile) + assert defined?(SiblingConstant) + assert ActiveSupport::Dependencies.autoloaded?(:MultipleConstantFile) + assert ActiveSupport::Dependencies.autoloaded?(:SiblingConstant) + + ActiveSupport::Dependencies.clear + + assert_not defined?(MultipleConstantFile) + assert_not defined?(SiblingConstant) + end + ensure + remove_constants(:MultipleConstantFile, :SiblingConstant) + end + + def test_nested_file_with_multiple_constants_and_require_dependency + with_autoloading_fixtures do + assert_not defined?(ClassFolder::NestedClass) + assert_not defined?(ClassFolder::SiblingClass) + + require_dependency "class_folder/nested_class" + + assert defined?(ClassFolder::NestedClass) + assert defined?(ClassFolder::SiblingClass) + assert ActiveSupport::Dependencies.autoloaded?("ClassFolder::NestedClass") + assert ActiveSupport::Dependencies.autoloaded?("ClassFolder::SiblingClass") + + ActiveSupport::Dependencies.clear + + assert_not defined?(ClassFolder::NestedClass) + assert_not defined?(ClassFolder::SiblingClass) + end + ensure + remove_constants(:ClassFolder) + end + + def test_nested_file_with_multiple_constants_and_auto_loading + with_autoloading_fixtures do + assert_not defined?(ClassFolder::NestedClass) + assert_not defined?(ClassFolder::SiblingClass) + + assert_kind_of Class, ClassFolder::NestedClass + + assert defined?(ClassFolder::NestedClass) + assert defined?(ClassFolder::SiblingClass) + assert ActiveSupport::Dependencies.autoloaded?("ClassFolder::NestedClass") + assert ActiveSupport::Dependencies.autoloaded?("ClassFolder::SiblingClass") + + ActiveSupport::Dependencies.clear + + assert_not defined?(ClassFolder::NestedClass) + assert_not defined?(ClassFolder::SiblingClass) + end + ensure + remove_constants(:ClassFolder) + end + + def test_autoload_doesnt_shadow_no_method_error_with_relative_constant + with_autoloading_fixtures do + assert_not defined?(::RaisesNoMethodError), "::RaisesNoMethodError is defined but it hasn't been referenced yet!" + 2.times do + assert_raise(NoMethodError) { RaisesNoMethodError } + assert_not defined?(::RaisesNoMethodError), "::RaisesNoMethodError is defined but it should have failed!" + end + end + ensure + remove_constants(:RaisesNoMethodError) + end + + def test_autoload_doesnt_shadow_no_method_error_with_absolute_constant + with_autoloading_fixtures do + assert_not defined?(::RaisesNoMethodError), "::RaisesNoMethodError is defined but it hasn't been referenced yet!" + 2.times do + assert_raise(NoMethodError) { ::RaisesNoMethodError } + assert_not defined?(::RaisesNoMethodError), "::RaisesNoMethodError is defined but it should have failed!" + end + end + ensure + remove_constants(:RaisesNoMethodError) + end + + def test_autoload_doesnt_shadow_error_when_mechanism_not_set_to_load + with_autoloading_fixtures do + ActiveSupport::Dependencies.mechanism = :require + 2.times do + assert_raise(NameError) { assert_equal 123, ::RaisesNameError::FooBarBaz } + end + end + ensure + remove_constants(:RaisesNameError) + end + + def test_autoload_doesnt_shadow_name_error + with_autoloading_fixtures do + 2.times do + e = assert_raise NameError do + ::RaisesNameError::FooBarBaz.object_id + end + assert_equal "uninitialized constant RaisesNameError::FooBarBaz", e.message + assert_not defined?(::RaisesNameError), "::RaisesNameError is defined but it should have failed!" + end + + assert_not defined?(::RaisesNameError) + 2.times do + assert_raise(NameError) { ::RaisesNameError } + assert_not defined?(::RaisesNameError), "::RaisesNameError is defined but it should have failed!" + end + end + ensure + remove_constants(:RaisesNameError) + end + + def test_remove_constant_handles_double_colon_at_start + Object.const_set "DeleteMe", Module.new + DeleteMe.const_set "OrMe", Module.new + ActiveSupport::Dependencies.remove_constant "::DeleteMe::OrMe" + assert_not defined?(DeleteMe::OrMe) + assert defined?(DeleteMe) + ActiveSupport::Dependencies.remove_constant "::DeleteMe" + assert_not defined?(DeleteMe) + ensure + remove_constants(:DeleteMe) + end + + def test_remove_constant_does_not_trigger_loading_autoloads + constant = "ShouldNotBeAutoloaded" + Object.class_eval do + autoload constant, File.expand_path("autoloading_fixtures/should_not_be_required", __dir__) + end + + assert_nil ActiveSupport::Dependencies.remove_constant(constant), "Kernel#autoload has been triggered by remove_constant" + assert_not defined?(ShouldNotBeAutoloaded) + ensure + remove_constants(constant) + end + + def test_remove_constant_does_not_autoload_already_removed_parents_as_a_side_effect + with_autoloading_fixtures do + _ = ::A # assignment to silence parse-time warning "possibly useless use of :: in void context" + _ = ::A::B # assignment to silence parse-time warning "possibly useless use of :: in void context" + ActiveSupport::Dependencies.remove_constant("A") + ActiveSupport::Dependencies.remove_constant("A::B") + assert_not defined?(A) + end + ensure + remove_constants(:A) + end + + def test_load_once_constants_should_not_be_unloaded + old_path = ActiveSupport::Dependencies.autoload_once_paths + with_autoloading_fixtures do + ActiveSupport::Dependencies.autoload_once_paths = ActiveSupport::Dependencies.autoload_paths + _ = ::A # assignment to silence parse-time warning "possibly useless use of :: in void context" + assert defined?(A) + ActiveSupport::Dependencies.clear + assert defined?(A) + end + ensure + ActiveSupport::Dependencies.autoload_once_paths = old_path + remove_constants(:A) + end + + def test_access_unloaded_constants_for_reload + with_autoloading_fixtures do + assert_kind_of Module, A + assert_kind_of Class, A::B # Necessary to load A::B for the test + ActiveSupport::Dependencies.mark_for_unload(A::B) + ActiveSupport::Dependencies.remove_unloadable_constants! + + A::B # Make sure no circular dependency error + end + ensure + remove_constants(:A) + end + + def test_autoload_once_paths_should_behave_when_recursively_loading + old_path = ActiveSupport::Dependencies.autoload_once_paths + with_loading "dependencies", "autoloading_fixtures" do + ActiveSupport::Dependencies.autoload_once_paths = [ActiveSupport::Dependencies.autoload_paths.last] + assert_not defined?(CrossSiteDependency) + assert_nothing_raised { CrossSiteDepender.nil? } + assert defined?(CrossSiteDependency) + assert_not ActiveSupport::Dependencies.autoloaded?(CrossSiteDependency), + "CrossSiteDependency shouldn't be marked as autoloaded!" + ActiveSupport::Dependencies.clear + assert defined?(CrossSiteDependency), + "CrossSiteDependency shouldn't have been unloaded!" + end + ensure + ActiveSupport::Dependencies.autoload_once_paths = old_path + remove_constants(:CrossSiteDependency) + end + + def test_hook_called_multiple_times + assert_nothing_raised { ActiveSupport::Dependencies.hook! } + end + + def test_load_and_require_stay_private + assert_includes Object.private_methods, :load + assert_includes Object.private_methods, :require + + ActiveSupport::Dependencies.unhook! + + assert_includes Object.private_methods, :load + assert_includes Object.private_methods, :require + ensure + ActiveSupport::Dependencies.hook! + end +end + +class DependenciesLogging < ActiveSupport::TestCase + MESSAGE = "message" + + def with_settings(logger, verbose) + original_logger = ActiveSupport::Dependencies.logger + original_verbose = ActiveSupport::Dependencies.verbose + + ActiveSupport::Dependencies.logger = logger + ActiveSupport::Dependencies.verbose = verbose + + yield + ensure + ActiveSupport::Dependencies.logger = original_logger + ActiveSupport::Dependencies.verbose = original_verbose + end + + def fake_logger + Class.new do + def self.debug(message) + message + end + end + end + + test "does not log if the logger is nil and verbose is false" do + with_settings(nil, false) do + assert_nil ActiveSupport::Dependencies.log(MESSAGE) + end + end + + test "does not log if the logger is nil and verbose is true" do + with_settings(nil, true) do + assert_nil ActiveSupport::Dependencies.log(MESSAGE) + end + end + + test "does not log if the logger is set and verbose is false" do + with_settings(fake_logger, false) do + assert_nil ActiveSupport::Dependencies.log(MESSAGE) + end + end + + test "logs if the logger is set and verbose is true" do + with_settings(fake_logger, true) do + assert_equal "autoloading: #{MESSAGE}", ActiveSupport::Dependencies.log(MESSAGE) + end + end +end |