1 require File.dirname(__FILE__) + '/abstract_unit'
4 module ModuleWithMissing
5 mattr_accessor :missing_count
6 def self.const_missing(name)
7 self.missing_count += 1
12 module ModuleWithConstant
13 InheritedConstant = "Hello"
16 class DependenciesTest < Test::Unit::TestCase
21 def with_loading(*from)
22 old_mechanism, Dependencies.mechanism = Dependencies.mechanism, :load
23 dir = File.dirname(__FILE__)
24 prior_load_paths = Dependencies.load_paths
25 Dependencies.load_paths = from.collect { |f| "#{dir}/#{f}" }
28 Dependencies.load_paths = prior_load_paths
29 Dependencies.mechanism = old_mechanism
30 Dependencies.explicitly_unloadable_constants = []
33 def test_tracking_loaded_files
34 require_dependency(File.dirname(__FILE__) + "/dependencies/service_one")
35 require_dependency(File.dirname(__FILE__) + "/dependencies/service_two")
36 assert_equal 2, Dependencies.loaded.size
39 def test_tracking_identical_loaded_files
40 require_dependency(File.dirname(__FILE__) + "/dependencies/service_one")
41 require_dependency(File.dirname(__FILE__) + "/dependencies/service_one")
42 assert_equal 1, Dependencies.loaded.size
45 def test_missing_dependency_raises_missing_source_file
46 assert_raises(MissingSourceFile) { require_dependency("missing_service") }
49 def test_missing_association_raises_nothing
50 assert_nothing_raised { require_association("missing_model") }
53 def test_dependency_which_raises_exception_isnt_added_to_loaded_set
55 filename = "#{File.dirname(__FILE__)}/dependencies/raises_exception"
56 $raises_exception_load_count = 0
60 require_dependency filename
61 flunk 'should have loaded dependencies/raises_exception which raises an exception'
63 assert_equal 'Loading me failed, so do not add to loaded or history.', e.message
65 assert_equal count + 1, $raises_exception_load_count
67 assert !Dependencies.loaded.include?(filename)
68 assert !Dependencies.history.include?(filename)
73 def test_warnings_should_be_enabled_on_first_load
74 with_loading 'dependencies' do
75 old_warnings, Dependencies.warnings_on_first_load = Dependencies.warnings_on_first_load, true
77 filename = "check_warnings"
78 expanded = File.expand_path("test/dependencies/#{filename}")
79 $check_warnings_load_count = 0
81 assert !Dependencies.loaded.include?(expanded)
82 assert !Dependencies.history.include?(expanded)
84 silence_warnings { require_dependency filename }
85 assert_equal 1, $check_warnings_load_count
86 assert_equal true, $checked_verbose, 'On first load warnings should be enabled.'
88 assert Dependencies.loaded.include?(expanded)
90 assert !Dependencies.loaded.include?(expanded)
91 assert Dependencies.history.include?(expanded)
93 silence_warnings { require_dependency filename }
94 assert_equal 2, $check_warnings_load_count
95 assert_equal nil, $checked_verbose, 'After first load warnings should be left alone.'
97 assert Dependencies.loaded.include?(expanded)
99 assert !Dependencies.loaded.include?(expanded)
100 assert Dependencies.history.include?(expanded)
102 enable_warnings { require_dependency filename }
103 assert_equal 3, $check_warnings_load_count
104 assert_equal true, $checked_verbose, 'After first load warnings should be left alone.'
106 assert Dependencies.loaded.include?(expanded)
110 def test_mutual_dependencies_dont_infinite_loop
111 with_loading 'dependencies' do
112 $mutual_dependencies_count = 0
113 assert_nothing_raised { require_dependency 'mutual_one' }
114 assert_equal 2, $mutual_dependencies_count
118 $mutual_dependencies_count = 0
119 assert_nothing_raised { require_dependency 'mutual_two' }
120 assert_equal 2, $mutual_dependencies_count
124 def test_as_load_path
125 assert_equal '', DependenciesTest.as_load_path
128 def test_module_loading
129 with_loading 'autoloading_fixtures' do
130 assert_kind_of Module, A
131 assert_kind_of Class, A::B
132 assert_kind_of Class, A::C::D
133 assert_kind_of Class, A::C::E::F
137 def test_non_existing_const_raises_name_error
138 with_loading 'autoloading_fixtures' do
139 assert_raises(NameError) { DoesNotExist }
140 assert_raises(NameError) { NoModule::DoesNotExist }
141 assert_raises(NameError) { A::DoesNotExist }
142 assert_raises(NameError) { A::B::DoesNotExist }
146 def test_directories_manifest_as_modules_unless_const_defined
147 with_loading 'autoloading_fixtures' do
148 assert_kind_of Module, ModuleFolder
149 Object.send! :remove_const, :ModuleFolder
153 def test_module_with_nested_class
154 with_loading 'autoloading_fixtures' do
155 assert_kind_of Class, ModuleFolder::NestedClass
156 Object.send! :remove_const, :ModuleFolder
160 def test_module_with_nested_inline_class
161 with_loading 'autoloading_fixtures' do
162 assert_kind_of Class, ModuleFolder::InlineClass
163 Object.send! :remove_const, :ModuleFolder
167 def test_directories_may_manifest_as_nested_classes
168 with_loading 'autoloading_fixtures' do
169 assert_kind_of Class, ClassFolder
170 Object.send! :remove_const, :ClassFolder
174 def test_class_with_nested_class
175 with_loading 'autoloading_fixtures' do
176 assert_kind_of Class, ClassFolder::NestedClass
177 Object.send! :remove_const, :ClassFolder
181 def test_class_with_nested_inline_class
182 with_loading 'autoloading_fixtures' do
183 assert_kind_of Class, ClassFolder::InlineClass
184 Object.send! :remove_const, :ClassFolder
188 def test_class_with_nested_inline_subclass_of_parent
189 with_loading 'autoloading_fixtures' do
190 assert_kind_of Class, ClassFolder::ClassFolderSubclass
191 assert_kind_of Class, ClassFolder
192 assert_equal 'indeed', ClassFolder::ClassFolderSubclass::ConstantInClassFolder
193 Object.send! :remove_const, :ClassFolder
197 def test_nested_class_can_access_sibling
198 with_loading 'autoloading_fixtures' do
199 sibling = ModuleFolder::NestedClass.class_eval "NestedSibling"
200 assert defined?(ModuleFolder::NestedSibling)
201 assert_equal ModuleFolder::NestedSibling, sibling
202 Object.send! :remove_const, :ModuleFolder
206 def failing_test_access_thru_and_upwards_fails
207 with_loading 'autoloading_fixtures' do
208 assert ! defined?(ModuleFolder)
209 assert_raises(NameError) { ModuleFolder::Object }
210 assert_raises(NameError) { ModuleFolder::NestedClass::Object }
211 Object.send! :remove_const, :ModuleFolder
215 def test_non_existing_const_raises_name_error_with_fully_qualified_name
216 with_loading 'autoloading_fixtures' do
220 rescue NameError => e
221 assert_equal "uninitialized constant A::DoesNotExist", e.message
224 A::B::DoesNotExist.nil?
226 rescue NameError => e
227 assert_equal "uninitialized constant A::B::DoesNotExist", e.message
232 def test_smart_name_error_strings
234 Object.module_eval "ImaginaryObject"
236 rescue NameError => e
237 assert e.message.include?("uninitialized constant ImaginaryObject")
241 def test_loadable_constants_for_path_should_handle_empty_autoloads
242 assert_equal [], Dependencies.loadable_constants_for_path('hello')
245 def test_loadable_constants_for_path_should_handle_relative_paths
246 fake_root = 'dependencies'
247 relative_root = File.dirname(__FILE__) + '/dependencies'
248 ['', '/'].each do |suffix|
249 with_loading fake_root + suffix do
250 assert_equal ["A::B"], Dependencies.loadable_constants_for_path(relative_root + '/a/b')
255 def test_loadable_constants_for_path_should_provide_all_results
256 fake_root = '/usr/apps/backpack'
257 with_loading fake_root, fake_root + '/lib' do
258 root = Dependencies.load_paths.first
259 assert_equal ["Lib::A::B", "A::B"], Dependencies.loadable_constants_for_path(root + '/lib/a/b')
263 def test_loadable_constants_for_path_should_uniq_results
264 fake_root = '/usr/apps/backpack/lib'
265 with_loading fake_root, fake_root + '/' do
266 root = Dependencies.load_paths.first
267 assert_equal ["A::B"], Dependencies.loadable_constants_for_path(root + '/a/b')
271 def test_loadable_constants_with_load_path_without_trailing_slash
272 path = File.dirname(__FILE__) + '/autoloading_fixtures/class_folder/inline_class.rb'
273 with_loading 'autoloading_fixtures/class/' do
274 assert_equal [], Dependencies.loadable_constants_for_path(path)
278 def test_qualified_const_defined
279 assert Dependencies.qualified_const_defined?("Object")
280 assert Dependencies.qualified_const_defined?("::Object")
281 assert Dependencies.qualified_const_defined?("::Object::Kernel")
282 assert Dependencies.qualified_const_defined?("::Object::Dependencies")
283 assert Dependencies.qualified_const_defined?("::Test::Unit::TestCase")
286 def test_qualified_const_defined_should_not_call_method_missing
287 ModuleWithMissing.missing_count = 0
288 assert ! Dependencies.qualified_const_defined?("ModuleWithMissing::A")
289 assert_equal 0, ModuleWithMissing.missing_count
290 assert ! Dependencies.qualified_const_defined?("ModuleWithMissing::A::B")
291 assert_equal 0, ModuleWithMissing.missing_count
295 with_loading 'autoloading_fixtures' do
296 assert ! Dependencies.autoloaded?("ModuleFolder")
297 assert ! Dependencies.autoloaded?("ModuleFolder::NestedClass")
299 assert Dependencies.autoloaded?(ModuleFolder)
301 assert Dependencies.autoloaded?("ModuleFolder")
302 assert ! Dependencies.autoloaded?("ModuleFolder::NestedClass")
304 assert Dependencies.autoloaded?(ModuleFolder::NestedClass)
306 assert Dependencies.autoloaded?("ModuleFolder")
307 assert Dependencies.autoloaded?("ModuleFolder::NestedClass")
309 assert Dependencies.autoloaded?("::ModuleFolder")
310 assert Dependencies.autoloaded?(:ModuleFolder)
312 # Anonymous modules aren't autoloaded.
313 assert !Dependencies.autoloaded?(Module.new)
315 nil_name = Module.new
316 def nil_name.name() nil end
317 assert !Dependencies.autoloaded?(nil_name)
319 Object.class_eval { remove_const :ModuleFolder }
323 def test_qualified_name_for
324 assert_equal "A", Dependencies.qualified_name_for(Object, :A)
325 assert_equal "A", Dependencies.qualified_name_for(:Object, :A)
326 assert_equal "A", Dependencies.qualified_name_for("Object", :A)
327 assert_equal "A", Dependencies.qualified_name_for("::Object", :A)
328 assert_equal "A", Dependencies.qualified_name_for("::Kernel", :A)
330 assert_equal "Dependencies::A", Dependencies.qualified_name_for(:Dependencies, :A)
331 assert_equal "Dependencies::A", Dependencies.qualified_name_for(Dependencies, :A)
335 with_loading 'dependencies' do
336 root = Dependencies.load_paths.first
337 assert_equal nil, Dependencies.search_for_file('service_three')
338 assert_equal nil, Dependencies.search_for_file('service_three.rb')
339 assert_equal root + '/service_one.rb', Dependencies.search_for_file('service_one')
340 assert_equal root + '/service_one.rb', Dependencies.search_for_file('service_one.rb')
344 def test_file_search_uses_first_in_load_path
345 with_loading 'dependencies', 'autoloading_fixtures' do
346 deps, autoload = Dependencies.load_paths
347 assert_match %r/dependencies/, deps
348 assert_match %r/autoloading_fixtures/, autoload
350 assert_equal deps + '/conflict.rb', Dependencies.search_for_file('conflict')
352 with_loading 'autoloading_fixtures', 'dependencies' do
353 autoload, deps = Dependencies.load_paths
354 assert_match %r/dependencies/, deps
355 assert_match %r/autoloading_fixtures/, autoload
357 assert_equal autoload + '/conflict.rb', Dependencies.search_for_file('conflict')
362 def test_custom_const_missing_should_work
363 Object.module_eval <<-end_eval
364 module ModuleWithCustomConstMissing
365 def self.const_missing(name)
366 const_set name, name.to_s.hash
374 with_loading 'autoloading_fixtures' do
375 assert_kind_of Integer, ::ModuleWithCustomConstMissing::B
376 assert_kind_of Module, ::ModuleWithCustomConstMissing::A
377 assert_kind_of String, ::ModuleWithCustomConstMissing::A::B
381 def test_const_missing_should_not_double_load
382 $counting_loaded_times = 0
383 with_loading 'autoloading_fixtures' do
384 require_dependency '././counting_loader'
385 assert_equal 1, $counting_loaded_times
386 assert_raises(ArgumentError) { Dependencies.load_missing_constant Object, :CountingLoader }
387 assert_equal 1, $counting_loaded_times
391 def test_const_missing_within_anonymous_module
392 $counting_loaded_times = 0
394 m.module_eval "def a() CountingLoader; end"
397 with_loading 'autoloading_fixtures' do
399 assert_nothing_raised { kls = a }
400 assert_equal "CountingLoader", kls.name
401 assert_equal 1, $counting_loaded_times
403 assert_nothing_raised { kls = a }
404 assert_equal 1, $counting_loaded_times
408 def test_removal_from_tree_should_be_detected
409 with_loading 'dependencies' do
410 root = Dependencies.load_paths.first
413 assert ! defined?(ServiceOne)
415 Dependencies.load_missing_constant(c, :FakeMissing)
416 flunk "Expected exception"
417 rescue ArgumentError => e
418 assert_match %r{ServiceOne has been removed from the module tree}i, e.message
423 def test_nested_load_error_isnt_rescued
424 with_loading 'dependencies' do
425 assert_raises(MissingSourceFile) do
431 def test_load_once_paths_do_not_add_to_autoloaded_constants
432 with_loading 'autoloading_fixtures' do
433 Dependencies.load_once_paths = Dependencies.load_paths.dup
435 assert ! Dependencies.autoloaded?("ModuleFolder")
436 assert ! Dependencies.autoloaded?("ModuleFolder::NestedClass")
437 assert ! Dependencies.autoloaded?(ModuleFolder)
439 1 if ModuleFolder::NestedClass # 1 if to avoid warning
440 assert ! Dependencies.autoloaded?(ModuleFolder::NestedClass)
443 Object.class_eval { remove_const :ModuleFolder }
444 Dependencies.load_once_paths = []
447 def test_application_should_special_case_application_controller
448 with_loading 'autoloading_fixtures' do
449 require_dependency 'application'
450 assert_equal 10, ApplicationController
451 assert Dependencies.autoloaded?(:ApplicationController)
455 def test_const_missing_on_kernel_should_fallback_to_object
456 with_loading 'autoloading_fixtures' do
458 assert_equal "E", kls.name
459 assert_equal kls.object_id, Kernel::E.object_id
463 def test_preexisting_constants_are_not_marked_as_autoloaded
464 with_loading 'autoloading_fixtures' do
465 require_dependency 'e'
466 assert Dependencies.autoloaded?(:E)
470 Object.const_set :E, Class.new
471 with_loading 'autoloading_fixtures' do
472 require_dependency 'e'
473 assert ! Dependencies.autoloaded?(:E), "E shouldn't be marked autoloaded!"
478 Object.class_eval { remove_const :E }
482 with_loading 'autoloading_fixtures' do
483 Object.const_set :M, Module.new
489 Object.const_set :M, Module.new
491 assert ! defined?(M), "Dependencies should unload unloadable constants each time"
495 def test_unloadable_should_fail_with_anonymous_modules
496 with_loading 'autoloading_fixtures' do
498 assert_raises(ArgumentError) { m.unloadable }
502 def test_unloadable_should_return_change_flag
503 with_loading 'autoloading_fixtures' do
504 Object.const_set :M, Module.new
505 assert_equal true, M.unloadable
506 assert_equal false, M.unloadable
510 def test_new_contants_in_without_constants
511 assert_equal [], (Dependencies.new_constants_in(Object) { })
512 assert Dependencies.constant_watch_stack.empty?
515 def test_new_constants_in_with_a_single_constant
516 assert_equal ["Hello"], Dependencies.new_constants_in(Object) {
517 Object.const_set :Hello, 10
519 assert Dependencies.constant_watch_stack.empty?
521 Object.class_eval { remove_const :Hello }
524 def test_new_constants_in_with_nesting
525 outer = Dependencies.new_constants_in(Object) do
526 Object.const_set :OuterBefore, 10
528 assert_equal ["Inner"], Dependencies.new_constants_in(Object) {
529 Object.const_set :Inner, 20
532 Object.const_set :OuterAfter, 30
535 assert_equal ["OuterAfter", "OuterBefore"], outer.sort.map(&:to_s)
536 assert Dependencies.constant_watch_stack.empty?
538 %w(OuterBefore Inner OuterAfter).each do |name|
539 Object.class_eval { remove_const name if const_defined?(name) }
543 def test_new_constants_in_module
544 Object.const_set :M, Module.new
546 outer = Dependencies.new_constants_in(M) do
547 M.const_set :OuterBefore, 10
549 inner = Dependencies.new_constants_in(M) do
550 M.const_set :Inner, 20
552 assert_equal ["M::Inner"], inner
554 M.const_set :OuterAfter, 30
556 assert_equal ["M::OuterAfter", "M::OuterBefore"], outer.sort
557 assert Dependencies.constant_watch_stack.empty?
559 Object.class_eval { remove_const :M }
562 def test_new_constants_in_module_using_name
563 outer = Dependencies.new_constants_in(:M) do
564 Object.const_set :M, Module.new
565 M.const_set :OuterBefore, 10
567 inner = Dependencies.new_constants_in(:M) do
568 M.const_set :Inner, 20
570 assert_equal ["M::Inner"], inner
572 M.const_set :OuterAfter, 30
574 assert_equal ["M::OuterAfter", "M::OuterBefore"], outer.sort
575 assert Dependencies.constant_watch_stack.empty?
577 Object.class_eval { remove_const :M }
580 def test_new_constants_in_with_inherited_constants
581 m = Dependencies.new_constants_in(:Object) do
582 Object.class_eval { include ModuleWithConstant }
587 def test_file_with_multiple_constants_and_require_dependency
588 with_loading 'autoloading_fixtures' do
589 assert ! defined?(MultipleConstantFile)
590 assert ! defined?(SiblingConstant)
592 require_dependency 'multiple_constant_file'
593 assert defined?(MultipleConstantFile)
594 assert defined?(SiblingConstant)
595 assert Dependencies.autoloaded?(:MultipleConstantFile)
596 assert Dependencies.autoloaded?(:SiblingConstant)
600 assert ! defined?(MultipleConstantFile)
601 assert ! defined?(SiblingConstant)
605 def test_file_with_multiple_constants_and_auto_loading
606 with_loading 'autoloading_fixtures' do
607 assert ! defined?(MultipleConstantFile)
608 assert ! defined?(SiblingConstant)
610 assert_equal 10, MultipleConstantFile
612 assert defined?(MultipleConstantFile)
613 assert defined?(SiblingConstant)
614 assert Dependencies.autoloaded?(:MultipleConstantFile)
615 assert Dependencies.autoloaded?(:SiblingConstant)
619 assert ! defined?(MultipleConstantFile)
620 assert ! defined?(SiblingConstant)
624 def test_nested_file_with_multiple_constants_and_require_dependency
625 with_loading 'autoloading_fixtures' do
626 assert ! defined?(ClassFolder::NestedClass)
627 assert ! defined?(ClassFolder::SiblingClass)
629 require_dependency 'class_folder/nested_class'
631 assert defined?(ClassFolder::NestedClass)
632 assert defined?(ClassFolder::SiblingClass)
633 assert Dependencies.autoloaded?("ClassFolder::NestedClass")
634 assert Dependencies.autoloaded?("ClassFolder::SiblingClass")
638 assert ! defined?(ClassFolder::NestedClass)
639 assert ! defined?(ClassFolder::SiblingClass)
643 def test_nested_file_with_multiple_constants_and_auto_loading
644 with_loading 'autoloading_fixtures' do
645 assert ! defined?(ClassFolder::NestedClass)
646 assert ! defined?(ClassFolder::SiblingClass)
648 assert_kind_of Class, ClassFolder::NestedClass
650 assert defined?(ClassFolder::NestedClass)
651 assert defined?(ClassFolder::SiblingClass)
652 assert Dependencies.autoloaded?("ClassFolder::NestedClass")
653 assert Dependencies.autoloaded?("ClassFolder::SiblingClass")
657 assert ! defined?(ClassFolder::NestedClass)
658 assert ! defined?(ClassFolder::SiblingClass)
662 def test_autoload_doesnt_shadow_no_method_error_with_relative_constant
663 with_loading 'autoloading_fixtures' do
664 assert !defined?(::RaisesNoMethodError), "::RaisesNoMethodError is defined but it hasn't been referenced yet!"
666 assert_raise(NoMethodError) { RaisesNoMethodError }
667 assert !defined?(::RaisesNoMethodError), "::RaisesNoMethodError is defined but it should have failed!"
672 Object.class_eval { remove_const :RaisesNoMethodError if const_defined?(:RaisesNoMethodError) }
675 def test_autoload_doesnt_shadow_no_method_error_with_absolute_constant
676 with_loading 'autoloading_fixtures' do
677 assert !defined?(::RaisesNoMethodError), "::RaisesNoMethodError is defined but it hasn't been referenced yet!"
679 assert_raise(NoMethodError) { ::RaisesNoMethodError }
680 assert !defined?(::RaisesNoMethodError), "::RaisesNoMethodError is defined but it should have failed!"
685 Object.class_eval { remove_const :RaisesNoMethodError if const_defined?(:RaisesNoMethodError) }
688 def test_autoload_doesnt_shadow_name_error
689 with_loading 'autoloading_fixtures' do
690 assert !defined?(::RaisesNameError), "::RaisesNameError is defined but it hasn't been referenced yet!"
693 ::RaisesNameError.object_id
694 flunk 'should have raised NameError when autoloaded file referenced FooBarBaz'
695 rescue NameError => e
696 assert_equal 'uninitialized constant RaisesNameError::FooBarBaz', e.message
698 assert !defined?(::RaisesNameError), "::RaisesNameError is defined but it should have failed!"
701 assert !defined?(RaisesNameError)
703 assert_raise(NameError) { RaisesNameError }
704 assert !defined?(::RaisesNameError), "::RaisesNameError is defined but it should have failed!"
709 Object.class_eval { remove_const :RaisesNoMethodError if const_defined?(:RaisesNoMethodError) }
712 def test_remove_constant_handles_double_colon_at_start
713 Object.const_set 'DeleteMe', Module.new
714 DeleteMe.const_set 'OrMe', Module.new
715 Dependencies.remove_constant "::DeleteMe::OrMe"
716 assert ! defined?(DeleteMe::OrMe)
717 assert defined?(DeleteMe)
718 Dependencies.remove_constant "::DeleteMe"
719 assert ! defined?(DeleteMe)
722 def test_load_once_constants_should_not_be_unloaded
723 with_loading 'autoloading_fixtures' do
724 Dependencies.load_once_paths = Dependencies.load_paths
731 Dependencies.load_once_paths = []
732 Object.class_eval { remove_const :A if const_defined?(:A) }
735 def test_load_once_paths_should_behave_when_recursively_loading
736 with_loading 'dependencies', 'autoloading_fixtures' do
737 Dependencies.load_once_paths = [Dependencies.load_paths.last]
738 assert !defined?(CrossSiteDependency)
739 assert_nothing_raised { CrossSiteDepender.nil? }
740 assert defined?(CrossSiteDependency)
741 assert !Dependencies.autoloaded?(CrossSiteDependency),
742 "CrossSiteDependency shouldn't be marked as autoloaded!"
744 assert defined?(CrossSiteDependency),
745 "CrossSiteDependency shouldn't have been unloaded!"
748 Dependencies.load_once_paths = []