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