1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00
ruby--ruby/spec/ruby/core/module/prepend_spec.rb

565 lines
13 KiB
Ruby
Raw Normal View History

require_relative '../../spec_helper'
require_relative 'fixtures/classes'
describe "Module#prepend" do
it "is a public method" do
Module.should have_public_instance_method(:prepend, false)
end
it "does not affect the superclass" do
Class.new { prepend Module.new }.superclass.should == Object
end
it "calls #prepend_features(self) in reversed order on each module" do
ScratchPad.record []
m = Module.new do
def self.prepend_features(mod)
ScratchPad << [ self, mod ]
end
end
m2 = Module.new do
def self.prepend_features(mod)
ScratchPad << [ self, mod ]
end
end
m3 = Module.new do
def self.prepend_features(mod)
ScratchPad << [ self, mod ]
end
end
c = Class.new { prepend(m, m2, m3) }
ScratchPad.recorded.should == [ [ m3, c], [ m2, c ], [ m, c ] ]
end
2021-06-02 14:34:07 +02:00
it "updates the method when a module is prepended" do
m_module = Module.new do
def foo
"m"
end
end
a_class = Class.new do
def foo
'a'
end
end
a = a_class.new
foo = -> { a.foo }
foo.call.should == 'a'
a_class.class_eval do
prepend m_module
end
foo.call.should == 'm'
end
it "updates the method when a prepended module is updated" do
m_module = Module.new
a_class = Class.new do
prepend m_module
def foo
'a'
end
end
a = a_class.new
foo = -> { a.foo }
foo.call.should == 'a'
m_module.module_eval do
def foo
"m"
end
end
foo.call.should == 'm'
end
it "updates the method when there is a base included method and the prepended module overrides it" do
base_module = Module.new do
def foo
'a'
end
end
a_class = Class.new do
include base_module
end
a = a_class.new
foo = -> { a.foo }
foo.call.should == 'a'
m_module = Module.new do
def foo
"m"
end
end
a_class.prepend m_module
foo.call.should == 'm'
end
it "updates the method when there is a base included method and the prepended module is later updated" do
base_module = Module.new do
def foo
'a'
end
end
a_class = Class.new do
include base_module
end
a = a_class.new
foo = -> { a.foo }
foo.call.should == 'a'
m_module = Module.new
a_class.prepend m_module
foo.call.should == 'a'
m_module.module_eval do
def foo
"m"
end
end
foo.call.should == 'm'
end
it "updates the method when a module prepended after a call is later updated" do
m_module = Module.new
a_class = Class.new do
def foo
'a'
end
end
a = a_class.new
foo = -> { a.foo }
foo.call.should == 'a'
a_class.prepend m_module
foo.call.should == 'a'
m_module.module_eval do
def foo
"m"
end
end
foo.call.should == 'm'
end
it "updates the method when a module is prepended after another and the method is defined later on that module" do
m_module = Module.new do
def foo
'a'
end
end
a_class = Class.new
a_class.prepend m_module
a = a_class.new
foo = -> { a.foo }
foo.call.should == 'a'
n_module = Module.new
a_class.prepend n_module
foo.call.should == 'a'
n_module.module_eval do
def foo
"n"
end
end
foo.call.should == 'n'
end
it "updates the method when a module is included in a prepended module and the method is defined later" do
a_class = Class.new
base_module = Module.new do
def foo
'a'
end
end
a_class.prepend base_module
a = a_class.new
foo = -> { a.foo }
foo.call.should == 'a'
m_module = Module.new
n_module = Module.new
m_module.include n_module
a_class.prepend m_module
n_module.module_eval do
def foo
"n"
end
end
foo.call.should == 'n'
end
it "updates the method when a new module with an included module is prepended" do
a_class = Class.new do
def foo
'a'
end
end
n_module = Module.new do
def foo
'n'
end
end
m_module = Module.new do
include n_module
end
a = a_class.new
foo = -> { a.foo }
foo.call.should == 'a'
a_class.class_eval do
prepend m_module
end
foo.call.should == 'n'
end
it "raises a TypeError when the argument is not a Module" do
2019-07-27 12:40:09 +02:00
-> { ModuleSpecs::Basic.prepend(Class.new) }.should raise_error(TypeError)
end
it "does not raise a TypeError when the argument is an instance of a subclass of Module" do
2019-07-27 12:40:09 +02:00
-> { ModuleSpecs::SubclassSpec.prepend(ModuleSpecs::Subclass.new) }.should_not raise_error(TypeError)
end
it "imports constants" do
m1 = Module.new
m1::MY_CONSTANT = 1
m2 = Module.new { prepend(m1) }
m2.constants.should include(:MY_CONSTANT)
end
it "imports instance methods" do
Module.new { prepend ModuleSpecs::A }.instance_methods.should include(:ma)
end
it "does not import methods to modules and classes" do
Module.new { prepend ModuleSpecs::A }.methods.should_not include(:ma)
end
it "allows wrapping methods" do
m = Module.new { def calc(x) super + 3 end }
c = Class.new { def calc(x) x*2 end }
c.prepend(m)
c.new.calc(1).should == 5
end
it "also prepends included modules" do
a = Module.new { def calc(x) x end }
b = Module.new { include a }
c = Class.new { prepend b }
c.new.calc(1).should == 1
end
it "prepends multiple modules in the right order" do
m1 = Module.new { def chain; super << :m1; end }
m2 = Module.new { def chain; super << :m2; end; prepend(m1) }
c = Class.new { def chain; [:c]; end; prepend(m2) }
c.new.chain.should == [:c, :m2, :m1]
end
it "includes prepended modules in ancestors" do
m = Module.new
Class.new { prepend(m) }.ancestors.should include(m)
end
it "reports the prepended module as the method owner" do
m = Module.new { def meth; end }
c = Class.new { def meth; end; prepend(m) }
c.new.method(:meth).owner.should == m
end
it "reports the prepended module as the unbound method owner" do
m = Module.new { def meth; end }
c = Class.new { def meth; end; prepend(m) }
c.instance_method(:meth).owner.should == m
c.public_instance_method(:meth).owner.should == m
end
it "causes the prepended module's method to be aliased by alias_method" do
m = Module.new { def meth; :m end }
c = Class.new { def meth; :c end; prepend(m); alias_method :alias, :meth }
c.new.alias.should == :m
end
it "reports the class for the owner of an aliased method on the class" do
m = Module.new
c = Class.new { prepend(m); def meth; :c end; alias_method :alias, :meth }
c.instance_method(:alias).owner.should == c
end
it "reports the class for the owner of a method aliased from the prepended module" do
m = Module.new { def meth; :m end }
c = Class.new { prepend(m); alias_method :alias, :meth }
c.instance_method(:alias).owner.should == c
end
it "sees an instance of a prepended class as kind of the prepended module" do
m = Module.new
c = Class.new { prepend(m) }
c.new.should be_kind_of(m)
end
it "keeps the module in the chain when dupping the class" do
m = Module.new
c = Class.new { prepend(m) }
c.dup.new.should be_kind_of(m)
end
2020-09-15 21:54:31 +02:00
ruby_version_is '0'...'3.0' do
Ensure origins for all included, prepended, and refined modules This fixes various issues when a module is included in or prepended to a module or class, and then refined, or refined and then included or prepended to a module or class. Implement by renaming ensure_origin to rb_ensure_origin, making it non-static, and calling it when refining a module. Fix Module#initialize_copy to handle origins correctly. Previously, Module#initialize_copy did not handle origins correctly. For example, this code: ```ruby module B; end class A def b; 2 end prepend B end a = A.dup.new class A def b; 1 end end p a.b ``` Printed 1 instead of 2. This is because the super chain for a.singleton_class was: ``` a.singleton_class A.dup B(iclass) B(iclass origin) A(origin) # not A.dup(origin) ``` The B iclasses would not be modified, so the includer entry would be still be set to A and not A.dup. This modifies things so that if the class/module has an origin, all iclasses between the class/module and the origin are duplicated and have the correct includer entry set, and the correct origin is created. This requires other changes to make sure all tests still pass: * rb_undef_methods_from doesn't automatically handle classes with origins, so pass it the origin for Comparable when undefing methods in Complex. This fixed a failure in the Complex tests. * When adding a method, the method cache was not cleared correctly if klass has an origin. Clear the method cache for the klass before switching to the origin of klass. This fixed failures in the autoload tests related to overridding require, without breaking the optimization tests. Also clear the method cache for both the module and origin when removing a method. * Module#include? is fixed to skip origin iclasses. * Refinements are fixed to use the origin class of the module that has an origin. * RCLASS_REFINED_BY_ANY is removed as it was only used in a single place and is no longer needed. * Marshal#dump is fixed to skip iclass origins. * rb_method_entry_make is fixed to handled overridden optimized methods for modules that have origins. Fixes [Bug #16852]
2020-05-23 20:16:27 -07:00
it "keeps the module in the chain when dupping an intermediate module" do
m1 = Module.new { def calc(x) x end }
m2 = Module.new { prepend(m1) }
c1 = Class.new { prepend(m2) }
m2dup = m2.dup
m2dup.ancestors.should == [m2dup,m1,m2]
c2 = Class.new { prepend(m2dup) }
c1.ancestors[0,3].should == [m1,m2,c1]
c1.new.should be_kind_of(m1)
c2.ancestors[0,4].should == [m2dup,m1,m2,c2]
c2.new.should be_kind_of(m1)
end
end
2020-09-15 21:54:31 +02:00
ruby_version_is '3.0' do
Ensure origins for all included, prepended, and refined modules This fixes various issues when a module is included in or prepended to a module or class, and then refined, or refined and then included or prepended to a module or class. Implement by renaming ensure_origin to rb_ensure_origin, making it non-static, and calling it when refining a module. Fix Module#initialize_copy to handle origins correctly. Previously, Module#initialize_copy did not handle origins correctly. For example, this code: ```ruby module B; end class A def b; 2 end prepend B end a = A.dup.new class A def b; 1 end end p a.b ``` Printed 1 instead of 2. This is because the super chain for a.singleton_class was: ``` a.singleton_class A.dup B(iclass) B(iclass origin) A(origin) # not A.dup(origin) ``` The B iclasses would not be modified, so the includer entry would be still be set to A and not A.dup. This modifies things so that if the class/module has an origin, all iclasses between the class/module and the origin are duplicated and have the correct includer entry set, and the correct origin is created. This requires other changes to make sure all tests still pass: * rb_undef_methods_from doesn't automatically handle classes with origins, so pass it the origin for Comparable when undefing methods in Complex. This fixed a failure in the Complex tests. * When adding a method, the method cache was not cleared correctly if klass has an origin. Clear the method cache for the klass before switching to the origin of klass. This fixed failures in the autoload tests related to overridding require, without breaking the optimization tests. Also clear the method cache for both the module and origin when removing a method. * Module#include? is fixed to skip origin iclasses. * Refinements are fixed to use the origin class of the module that has an origin. * RCLASS_REFINED_BY_ANY is removed as it was only used in a single place and is no longer needed. * Marshal#dump is fixed to skip iclass origins. * rb_method_entry_make is fixed to handled overridden optimized methods for modules that have origins. Fixes [Bug #16852]
2020-05-23 20:16:27 -07:00
it "uses only new module when dupping the module" do
m1 = Module.new { def calc(x) x end }
m2 = Module.new { prepend(m1) }
c1 = Class.new { prepend(m2) }
m2dup = m2.dup
m2dup.ancestors.should == [m1,m2dup]
c2 = Class.new { prepend(m2dup) }
c1.ancestors[0,3].should == [m1,m2,c1]
c1.new.should be_kind_of(m1)
c2.ancestors[0,3].should == [m1,m2dup,c2]
c2.new.should be_kind_of(m1)
end
end
it "depends on prepend_features to add the module" do
m = Module.new { def self.prepend_features(mod) end }
Class.new { prepend(m) }.ancestors.should_not include(m)
end
it "adds the module in the subclass chains" do
parent = Class.new { def chain; [:parent]; end }
child = Class.new(parent) { def chain; super << :child; end }
mod = Module.new { def chain; super << :mod; end }
parent.prepend(mod)
parent.ancestors[0,2].should == [mod, parent]
child.ancestors[0,3].should == [child, mod, parent]
parent.new.chain.should == [:parent, :mod]
child.new.chain.should == [:parent, :mod, :child]
end
it "inserts a later prepended module into the chain" do
m1 = Module.new { def chain; super << :m1; end }
m2 = Module.new { def chain; super << :m2; end }
c1 = Class.new { def chain; [:c1]; end; prepend m1 }
c2 = Class.new(c1) { def chain; super << :c2; end }
c2.new.chain.should == [:c1, :m1, :c2]
c1.prepend(m2)
c2.new.chain.should == [:c1, :m1, :m2, :c2]
end
it "works with subclasses" do
m = Module.new do
def chain
super << :module
end
end
c = Class.new do
prepend m
def chain
[:class]
end
end
s = Class.new(c) do
def chain
super << :subclass
end
end
s.new.chain.should == [:class, :module, :subclass]
end
it "throws a NoMethodError when there is no more superclass" do
m = Module.new do
def chain
super << :module
end
end
c = Class.new do
prepend m
def chain
super << :class
end
end
2019-07-27 12:40:09 +02:00
-> { c.new.chain }.should raise_error(NoMethodError)
end
it "calls prepended after prepend_features" do
ScratchPad.record []
m = Module.new do
def self.prepend_features(klass)
ScratchPad << [:prepend_features, klass]
end
def self.prepended(klass)
ScratchPad << [:prepended, klass]
end
end
c = Class.new { prepend(m) }
ScratchPad.recorded.should == [[:prepend_features, c], [:prepended, c]]
end
it "detects cyclic prepends" do
2019-07-27 12:40:09 +02:00
-> {
module ModuleSpecs::P
prepend ModuleSpecs::P
end
}.should raise_error(ArgumentError)
end
2019-04-27 18:53:23 +02:00
it "doesn't accept no-arguments" do
2019-07-27 12:40:09 +02:00
-> {
2019-04-27 18:53:23 +02:00
Module.new do
prepend
end
}.should raise_error(ArgumentError)
end
it "returns the class it's included into" do
m = Module.new
r = nil
c = Class.new { r = prepend m }
r.should == c
end
it "clears any caches" do
module ModuleSpecs::M3
module PM1
def foo
:m1
end
end
module PM2
def foo
:m2
end
end
klass = Class.new do
prepend PM1
def get
foo
end
end
o = klass.new
o.get.should == :m1
klass.class_eval do
prepend PM2
end
o.get.should == :m2
end
end
it "supports super when the module is prepended into a singleton class" do
ScratchPad.record []
mod = Module.new do
def self.inherited(base)
super
end
end
module_with_singleton_class_prepend = Module.new do
singleton_class.prepend(mod)
end
klass = Class.new(ModuleSpecs::RecordIncludedModules) do
include module_with_singleton_class_prepend
end
ScratchPad.recorded.should == klass
end
it "supports super when the module is prepended into a singleton class with a class super" do
ScratchPad.record []
base_class = Class.new(ModuleSpecs::RecordIncludedModules) do
def self.inherited(base)
super
end
end
prepended_module = Module.new
base_class.singleton_class.prepend(prepended_module)
child_class = Class.new(base_class)
ScratchPad.recorded.should == child_class
end
it "does not interfere with a define_method super in the original class" do
base_class = Class.new do
def foo(ary)
ary << 1
end
end
child_class = Class.new(base_class) do
define_method :foo do |ary|
ary << 2
super(ary)
end
end
prep_mod = Module.new do
def foo(ary)
ary << 3
super(ary)
end
end
child_class.prepend(prep_mod)
ary = []
child_class.new.foo(ary)
ary.should == [3, 2, 1]
end
describe "called on a module" do
describe "included into a class"
it "does not obscure the module's methods from reflective access" do
mod = Module.new do
def foo; end
end
cls = Class.new do
include mod
end
pre = Module.new
mod.prepend pre
cls.instance_methods.should include(:foo)
end
end
end