summaryrefslogtreecommitdiff
path: root/spec/ruby/core/kernel/clone_spec.rb
blob: 38ae1984c09a091651605b53e75a89b0dd537418 (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
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
require_relative '../../spec_helper'
require_relative 'fixtures/classes'
require_relative 'shared/dup_clone'

describe "Kernel#clone" do
  it_behaves_like :kernel_dup_clone, :clone

  before :each do
    ScratchPad.clear
    @obj = KernelSpecs::Duplicate.new 1, :a
  end

  it "calls #initialize_copy on the new instance" do
    clone = @obj.clone
    ScratchPad.recorded.should_not == @obj.object_id
    ScratchPad.recorded.should == clone.object_id
  end

  it "uses the internal allocator and does not call #allocate" do
    klass = Class.new
    instance = klass.new

    def klass.allocate
      raise "allocate should not be called"
    end

    clone = instance.clone
    clone.class.should equal klass
  end

  describe "with no arguments" do
    it "copies frozen state from the original" do
      o2 = @obj.clone
      o2.should_not.frozen?

      @obj.freeze
      o3 = @obj.clone
      o3.should.frozen?
    end

    it 'copies frozen?' do
      o = ''.freeze.clone
      o.frozen?.should be_true
    end
  end

  describe "with freeze: nil" do
    ruby_version_is ""..."3.0" do
      it "raises ArgumentError" do
        -> { @obj.clone(freeze: nil) }.should raise_error(ArgumentError, /unexpected value for freeze: NilClass/)
      end
    end

    ruby_version_is "3.0" do
      it "copies frozen state from the original, like #clone without arguments" do
        o2 = @obj.clone(freeze: nil)
        o2.should_not.frozen?

        @obj.freeze
        o3 = @obj.clone(freeze: nil)
        o3.should.frozen?
      end

      it "copies frozen?" do
        o = "".freeze.clone(freeze: nil)
        o.frozen?.should be_true
      end
    end
  end

  describe "with freeze: true" do
    it 'makes a frozen copy if the original is frozen' do
      @obj.freeze
      @obj.clone(freeze: true).should.frozen?
    end

    ruby_version_is ''...'3.0' do
      it 'does not freeze the copy even if the original is not frozen' do
        @obj.clone(freeze: true).should_not.frozen?
      end

      it "calls #initialize_clone with no kwargs" do
        obj = KernelSpecs::CloneFreeze.new
        obj.clone(freeze: true)
        ScratchPad.recorded.should == [obj, {}]
      end
    end

    ruby_version_is '3.0' do
      it 'freezes the copy even if the original was not frozen' do
        @obj.clone(freeze: true).should.frozen?
      end

      it "calls #initialize_clone with kwargs freeze: true" do
        obj = KernelSpecs::CloneFreeze.new
        obj.clone(freeze: true)
        ScratchPad.recorded.should == [obj, { freeze: true }]
      end

      it "calls #initialize_clone with kwargs freeze: true even if #initialize_clone only takes a single argument" do
        obj = KernelSpecs::Clone.new
        -> { obj.clone(freeze: true) }.should raise_error(ArgumentError, 'wrong number of arguments (given 2, expected 1)')
      end
    end
  end

  describe "with freeze: false" do
    it 'does not freeze the copy if the original is frozen' do
      @obj.freeze
      @obj.clone(freeze: false).should_not.frozen?
    end

    it 'does not freeze the copy if the original is not frozen' do
      @obj.clone(freeze: false).should_not.frozen?
    end

    ruby_version_is ''...'3.0' do
      it "calls #initialize_clone with no kwargs" do
        obj = KernelSpecs::CloneFreeze.new
        obj.clone(freeze: false)
        ScratchPad.recorded.should == [obj, {}]
      end
    end

    ruby_version_is '3.0' do
      it "calls #initialize_clone with kwargs freeze: false" do
        obj = KernelSpecs::CloneFreeze.new
        obj.clone(freeze: false)
        ScratchPad.recorded.should == [obj, { freeze: false }]
      end

      it "calls #initialize_clone with kwargs freeze: false even if #initialize_clone only takes a single argument" do
        obj = KernelSpecs::Clone.new
        -> { obj.clone(freeze: false) }.should raise_error(ArgumentError, 'wrong number of arguments (given 2, expected 1)')
      end
    end
  end

  describe "with freeze: anything else" do
    it 'raises ArgumentError when passed not true/false/nil' do
      -> { @obj.clone(freeze: 1) }.should raise_error(ArgumentError, /unexpected value for freeze: Integer/)
      -> { @obj.clone(freeze: "") }.should raise_error(ArgumentError, /unexpected value for freeze: String/)
      -> { @obj.clone(freeze: Object.new) }.should raise_error(ArgumentError, /unexpected value for freeze: Object/)
    end
  end

  it "copies instance variables" do
    clone = @obj.clone
    clone.one.should == 1
    clone.two.should == :a
  end

  it "copies singleton methods" do
    def @obj.special() :the_one end
    clone = @obj.clone
    clone.special.should == :the_one
  end

  it "copies modules included in the singleton class" do
    class << @obj
      include KernelSpecs::DuplicateM
    end

    clone = @obj.clone
    clone.repr.should == "KernelSpecs::Duplicate"
  end

  it "copies constants defined in the singleton class" do
    class << @obj
      CLONE = :clone
    end

    clone = @obj.clone
    class << clone
      CLONE.should == :clone
    end
  end

  it "replaces a singleton object's metaclass with a new copy with the same superclass" do
    cls = Class.new do
      def bar
        ['a']
      end
    end

    object = cls.new
    object.define_singleton_method(:bar) do
      ['b', *super()]
    end
    object.bar.should == ['b', 'a']

    cloned = object.clone

    cloned.singleton_methods.should == [:bar]

    # bar should replace previous one
    cloned.define_singleton_method(:bar) do
      ['c', *super()]
    end
    cloned.bar.should == ['c', 'a']

    # bar should be removed and call through to superclass
    cloned.singleton_class.class_eval do
      remove_method :bar
    end

    cloned.bar.should == ['a']
  end

  ruby_version_is ''...'2.7' do
    it 'copies tainted?' do
      o = ''.taint.clone
      o.tainted?.should be_true
    end
  end
end