summaryrefslogtreecommitdiff
path: root/spec/ruby/core/fiber/storage_spec.rb
blob: 015caaf3bbff489c2ebcece015b01673de0a7fdf (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
require_relative '../../spec_helper'

describe "Fiber.new(storage:)" do
  it "creates a Fiber with the given storage" do
    storage = {life: 42}
    fiber = Fiber.new(storage: storage) { Fiber.current.storage }
    fiber.resume.should == storage
  end

  it "creates a fiber with lazily initialized storage" do
    Fiber.new(storage: nil) { Fiber[:x] = 10; Fiber.current.storage }.resume.should == {x: 10}
  end

  it "creates a fiber by inheriting the storage of the parent fiber" do
    fiber = Fiber.new(storage: {life: 42}) do
      Fiber.new { Fiber.current.storage }.resume
    end
    fiber.resume.should == {life: 42}
  end

  it "cannot create a fiber with non-hash storage" do
    -> { Fiber.new(storage: 42) {} }.should raise_error(TypeError)
  end

  it "cannot create a fiber with a frozen hash as storage" do
    -> { Fiber.new(storage: {life: 43}.freeze) {} }.should raise_error(FrozenError)
  end

  it "cannot create a fiber with a storage hash with non-symbol keys" do
    -> { Fiber.new(storage: {life: 43, Object.new => 44}) {} }.should raise_error(TypeError)
  end
end

describe "Fiber#storage" do
  it "cannot be accessed from a different fiber" do
    f = Fiber.new(storage: {life: 42}) { nil }
    -> {
      f.storage
    }.should raise_error(ArgumentError, /Fiber storage can only be accessed from the Fiber it belongs to/)
  end
end

describe "Fiber#storage=" do
  it "can clear the storage of the fiber" do
    fiber = Fiber.new(storage: {life: 42}) do
      Fiber.current.storage = nil
      Fiber[:x] = 10
      Fiber.current.storage
    end
    fiber.resume.should == {x: 10}
  end

  it "can set the storage of the fiber" do
    fiber = Fiber.new(storage: {life: 42}) do
      Fiber.current.storage = {life: 43}
      Fiber.current.storage
    end
    fiber.resume.should == {life: 43}
  end

  it "can't set the storage of the fiber to non-hash" do
    -> { Fiber.current.storage = 42 }.should raise_error(TypeError)
  end

  it "can't set the storage of the fiber to a frozen hash" do
    -> { Fiber.current.storage = {life: 43}.freeze }.should raise_error(FrozenError)
  end

  it "can't set the storage of the fiber to a hash with non-symbol keys" do
    -> { Fiber.current.storage = {life: 43, Object.new => 44} }.should raise_error(TypeError)
  end
end

describe "Fiber.[]" do
  it "returns the value of the given key in the storage of the current fiber" do
    Fiber.new(storage: {life: 42}) { Fiber[:life] }.resume.should == 42
  end

  it "returns nil if the key is not present in the storage of the current fiber" do
    Fiber.new(storage: {life: 42}) { Fiber[:death] }.resume.should be_nil
  end

  it "returns nil if the current fiber has no storage" do
    Fiber.new { Fiber[:life] }.resume.should be_nil
  end

  ruby_version_is "3.2.3" do
    it "can use dynamically defined keys" do
      key = :"#{self.class.name}#.#{self.object_id}"
      Fiber.new { Fiber[key] = 42; Fiber[key] }.resume.should == 42
    end

    it "can't use invalid keys" do
      invalid_keys = [Object.new, 12]
      invalid_keys.each do |key|
        -> { Fiber[key] }.should raise_error(TypeError)
      end
    end
  end

  ruby_bug "#20978", ""..."3.4" do
    it "can use keys as strings" do
      key = Object.new
      def key.to_str; "Foo"; end
      Fiber.new { Fiber[key] = 42; Fiber["Foo"] }.resume.should == 42
    end

    it "converts a String key into a Symbol" do
      Fiber.new { Fiber["key"] = 42; Fiber[:key] }.resume.should == 42
      Fiber.new { Fiber[:key] = 42; Fiber["key"] }.resume.should == 42
    end

    it "can use any object that responds to #to_str as a key" do
      key = mock("key")
      key.should_receive(:to_str).twice.and_return("key")
      Fiber.new { Fiber[key] = 42; Fiber[key] }.resume.should == 42
    end
  end

  it "does not call #to_sym on the key" do
    key = mock("key")
    key.should_not_receive(:to_sym)
    -> { Fiber[key] }.should raise_error(TypeError)
  end

  it "can access the storage of the parent fiber" do
    f = Fiber.new(storage: {life: 42}) do
      Fiber.new { Fiber[:life] }.resume
    end
    f.resume.should == 42
  end

  it "can't access the storage of the fiber with non-symbol keys" do
    -> { Fiber[Object.new] }.should raise_error(TypeError)
  end
end

describe "Fiber.[]=" do
  it "sets the value of the given key in the storage of the current fiber" do
    Fiber.new(storage: {life: 42}) { Fiber[:life] = 43; Fiber[:life] }.resume.should == 43
  end

  it "sets the value of the given key in the storage of the current fiber" do
    Fiber.new(storage: {life: 42}) { Fiber[:death] = 43; Fiber[:death] }.resume.should == 43
  end

  it "sets the value of the given key in the storage of the current fiber" do
    Fiber.new { Fiber[:life] = 43; Fiber[:life] }.resume.should == 43
  end

  it "does not overwrite the storage of the parent fiber" do
    f = Fiber.new(storage: {life: 42}) do
      Fiber.yield Fiber.new { Fiber[:life] = 43; Fiber[:life] }.resume
      Fiber[:life]
    end
    f.resume.should == 43 # Value of the inner fiber
    f.resume.should == 42 # Value of the outer fiber
  end

  it "can't access the storage of the fiber with non-symbol keys" do
    -> { Fiber[Object.new] = 44 }.should raise_error(TypeError)
  end

  ruby_version_is "3.3" do
    it "deletes the fiber storage key when assigning nil" do
      Fiber.new(storage: {life: 42}) {
        Fiber[:life] = nil
        Fiber.current.storage
      }.resume.should == {}
    end
  end
end

describe "Thread.new" do
  it "creates a thread with the storage of the current fiber" do
    fiber = Fiber.new(storage: {life: 42}) do
      Thread.new { Fiber.current.storage }.value
    end
    fiber.resume.should == {life: 42}
  end
end