summaryrefslogtreecommitdiff
path: root/spec/ruby/core/module/shared/class_eval.rb
blob: b1d5cb3814edea34317ed2602e3331b550622ebc (plain)
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