|
1 | 1 | require_relative '../../spec_helper' |
2 | 2 | require_relative 'fixtures/classes' |
3 | | -require_relative 'shared/class_eval' |
4 | 3 |
|
5 | 4 | describe "Module#module_eval" do |
6 | | - it_behaves_like :module_class_eval, :module_eval |
| 5 | + # TODO: This should probably be replaced with a "should behave like" that uses |
| 6 | + # the many scoping/binding specs from kernel/eval_spec, since most of those |
| 7 | + # behaviors are the same for instance_eval. See also module_eval/class_eval. |
| 8 | + |
| 9 | + it "evaluates a given string in the context of self" do |
| 10 | + ModuleSpecs.module_eval("self").should == ModuleSpecs |
| 11 | + ModuleSpecs.module_eval("1 + 1").should == 2 |
| 12 | + end |
| 13 | + |
| 14 | + it "does not add defined methods to other classes" do |
| 15 | + FalseClass.module_eval do |
| 16 | + def foo |
| 17 | + 'foo' |
| 18 | + end |
| 19 | + end |
| 20 | + -> {42.foo}.should.raise(NoMethodError) |
| 21 | + end |
| 22 | + |
| 23 | + it "resolves constants in the caller scope" do |
| 24 | + ModuleSpecs::ClassEvalTest.get_constant_from_scope.should == ModuleSpecs::Lookup |
| 25 | + end |
| 26 | + |
| 27 | + it "resolves constants in the caller scope ignoring send" do |
| 28 | + ModuleSpecs::ClassEvalTest.get_constant_from_scope_with_send(:module_eval).should == ModuleSpecs::Lookup |
| 29 | + end |
| 30 | + |
| 31 | + it "resolves constants in the receiver's scope" do |
| 32 | + ModuleSpecs.module_eval("Lookup").should == ModuleSpecs::Lookup |
| 33 | + ModuleSpecs.module_eval("Lookup::LOOKIE").should == ModuleSpecs::Lookup::LOOKIE |
| 34 | + end |
| 35 | + |
| 36 | + it "defines constants in the receiver's scope" do |
| 37 | + ModuleSpecs.module_eval("module NewEvaluatedModule;end") |
| 38 | + ModuleSpecs.const_defined?(:NewEvaluatedModule, false).should == true |
| 39 | + end |
| 40 | + |
| 41 | + it "evaluates a given block in the context of self" do |
| 42 | + ModuleSpecs.module_eval { self }.should == ModuleSpecs |
| 43 | + ModuleSpecs.module_eval { 1 + 1 }.should == 2 |
| 44 | + end |
| 45 | + |
| 46 | + it "passes the module as the first argument of the block" do |
| 47 | + given = nil |
| 48 | + ModuleSpecs.module_eval do |block_parameter| |
| 49 | + given = block_parameter |
| 50 | + end |
| 51 | + given.should.equal? ModuleSpecs |
| 52 | + end |
| 53 | + |
| 54 | + it "uses the optional filename and lineno parameters for error messages" do |
| 55 | + ModuleSpecs.module_eval("[__FILE__, __LINE__]", "test", 102).should == ["test", 102] |
| 56 | + end |
| 57 | + |
| 58 | + it "uses the caller location as default filename" do |
| 59 | + ModuleSpecs.module_eval("[__FILE__, __LINE__]").should == ["(eval at #{__FILE__}:#{__LINE__})", 1] |
| 60 | + end |
| 61 | + |
| 62 | + it "converts a non-string filename to a string using to_str" do |
| 63 | + (file = mock(__FILE__)).should_receive(:to_str).and_return(__FILE__) |
| 64 | + ModuleSpecs.module_eval("1+1", file) |
| 65 | + |
| 66 | + (file = mock(__FILE__)).should_receive(:to_str).and_return(__FILE__) |
| 67 | + ModuleSpecs.module_eval("1+1", file, 15) |
| 68 | + end |
| 69 | + |
| 70 | + it "raises a TypeError when the given filename can't be converted to string using to_str" do |
| 71 | + (file = mock('123')).should_receive(:to_str).and_return(123) |
| 72 | + -> { ModuleSpecs.module_eval("1+1", file) }.should raise_consistent_error(TypeError, /can't convert MockObject into String/) |
| 73 | + end |
| 74 | + |
| 75 | + it "converts non string eval-string to string using to_str" do |
| 76 | + (o = mock('1 + 1')).should_receive(:to_str).and_return("1 + 1") |
| 77 | + ModuleSpecs.module_eval(o).should == 2 |
| 78 | + |
| 79 | + (o = mock('1 + 1')).should_receive(:to_str).and_return("1 + 1") |
| 80 | + ModuleSpecs.module_eval(o, "file.rb").should == 2 |
| 81 | + |
| 82 | + (o = mock('1 + 1')).should_receive(:to_str).and_return("1 + 1") |
| 83 | + ModuleSpecs.module_eval(o, "file.rb", 15).should == 2 |
| 84 | + end |
| 85 | + |
| 86 | + it "raises a TypeError when the given eval-string can't be converted to string using to_str" do |
| 87 | + o = mock('x') |
| 88 | + -> { ModuleSpecs.module_eval(o) }.should.raise(TypeError, "no implicit conversion of MockObject into String") |
| 89 | + |
| 90 | + (o = mock('123')).should_receive(:to_str).and_return(123) |
| 91 | + -> { ModuleSpecs.module_eval(o) }.should raise_consistent_error(TypeError, /can't convert MockObject into String/) |
| 92 | + end |
| 93 | + |
| 94 | + it "raises an ArgumentError when no arguments and no block are given" do |
| 95 | + -> { ModuleSpecs.module_eval }.should.raise(ArgumentError, "wrong number of arguments (given 0, expected 1..3)") |
| 96 | + end |
| 97 | + |
| 98 | + it "raises an ArgumentError when more than 3 arguments are given" do |
| 99 | + -> { |
| 100 | + ModuleSpecs.module_eval("1 + 1", "some file", 0, "bogus") |
| 101 | + }.should.raise(ArgumentError, "wrong number of arguments (given 4, expected 1..3)") |
| 102 | + end |
| 103 | + |
| 104 | + it "raises an ArgumentError when a block and normal arguments are given" do |
| 105 | + -> { |
| 106 | + ModuleSpecs.module_eval("1 + 1") { 1 + 1 } |
| 107 | + }.should.raise(ArgumentError, "wrong number of arguments (given 1, expected 0)") |
| 108 | + end |
| 109 | + |
| 110 | + # This case was found because Rubinius was caching the compiled |
| 111 | + # version of the string and not duping the methods within the |
| 112 | + # eval, causing the method addition to change the static scope |
| 113 | + # of the shared CompiledCode. |
| 114 | + it "adds methods respecting the lexical constant scope" do |
| 115 | + code = "def self.attribute; C; end" |
| 116 | + |
| 117 | + a = Class.new do |
| 118 | + self::C = "A" |
| 119 | + end |
| 120 | + |
| 121 | + b = Class.new do |
| 122 | + self::C = "B" |
| 123 | + end |
| 124 | + |
| 125 | + a.module_eval(code) |
| 126 | + b.module_eval(code) |
| 127 | + |
| 128 | + a.attribute.should == "A" |
| 129 | + b.attribute.should == "B" |
| 130 | + end |
| 131 | + |
| 132 | + it "activates refinements from the eval scope" do |
| 133 | + refinery = Module.new do |
| 134 | + refine ModuleSpecs::NamedClass do |
| 135 | + def foo |
| 136 | + "bar" |
| 137 | + end |
| 138 | + end |
| 139 | + end |
| 140 | + |
| 141 | + mid = :module_eval |
| 142 | + result = nil |
| 143 | + |
| 144 | + Class.new do |
| 145 | + using refinery |
| 146 | + |
| 147 | + result = send(mid, "ModuleSpecs::NamedClass.new.foo") |
| 148 | + end |
| 149 | + |
| 150 | + result.should == "bar" |
| 151 | + end |
| 152 | + |
| 153 | + it "activates refinements from the eval scope with block" do |
| 154 | + refinery = Module.new do |
| 155 | + refine ModuleSpecs::NamedClass do |
| 156 | + def foo |
| 157 | + "bar" |
| 158 | + end |
| 159 | + end |
| 160 | + end |
| 161 | + |
| 162 | + mid = :module_eval |
| 163 | + result = nil |
| 164 | + |
| 165 | + Class.new do |
| 166 | + using refinery |
| 167 | + |
| 168 | + result = send(mid) do |
| 169 | + ModuleSpecs::NamedClass.new.foo |
| 170 | + end |
| 171 | + end |
| 172 | + |
| 173 | + result.should == "bar" |
| 174 | + end |
7 | 175 | end |
0 commit comments