ractor-shim 0.0.1 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +24 -3
- data/Rakefile +5 -1
- data/lib/ractor/shim.rb +422 -1
- data/test/methods/Ractor.singleton_class.txt +16 -0
- data/test/methods/Ractor.txt +13 -0
- data/test/methods/Ractor::Port.singleton_class.txt +0 -0
- data/test/methods/Ractor::Port.txt +6 -0
- data/test/ractor_methods_test.rb +83 -0
- data/test/run_tests.rb +249 -0
- data/test/test_ractor.rb +2345 -0
- metadata +8 -2
- data/lib/ractor/shim/version.rb +0 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ba3ed0a7cfb270c23651d31c50286068defa1b0492459286cabc055da64ae2c8
|
|
4
|
+
data.tar.gz: 98ed41fb52ac1676960b4d6a49d49e66920c4465a779da9b1e5f12073427cee6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 55647c412c0fdecac6feb0b7fcf353c86d2114979eb6aa9ee8c1c0521c3a05e35123ea9db82ba2d808f483bfe244add8b2140cad1267121d8f1cfdeb329250c9
|
|
7
|
+
data.tar.gz: 10dd59ab7425af30137bb2fa0edf416447cdd01a16aa6601d3df57a396b1833638e78197ca4e827169223ab23d9a94fe48f7ef6ff9f8e85522303a6cd8e9dc6a
|
data/README.md
CHANGED
|
@@ -1,18 +1,39 @@
|
|
|
1
1
|
# ractor-shim
|
|
2
2
|
|
|
3
|
-
A shim to define `Ractor` by using `Thread`, if `Ractor` is not already defined.
|
|
3
|
+
A shim to define `Ractor` by using `Thread`, `Queue`, etc if `Ractor` is not already defined.
|
|
4
4
|
|
|
5
|
-
This is notably useful to run programs
|
|
5
|
+
This is notably useful to run programs relying on `Ractor` on Ruby implementations which don't define `Ractor` such as TruffleRuby and JRuby.
|
|
6
|
+
|
|
7
|
+
Note that TruffleRuby and JRuby both run Ruby code in threads in parallel, so this gem enables using the Ractor API on these Rubies and run Ractors in parallel.
|
|
8
|
+
|
|
9
|
+
The gem also provides the Ruby 3.5 Ractor API (`Ractor::Port`, `Ractor#{join,value,monitor}`, etc) for CRuby 2.7 to 3.4.
|
|
10
|
+
|
|
11
|
+
When `Ractor` is not already defined, this gem implements `Ractor.make_shareable(object)` by just returning `object` to avoid unnecessary overhead.
|
|
12
|
+
The main reason is that the CRuby implementation of `Ractor.make_shareable` returns an immutable object, so it won't be mutated by the program anyway and there is no need to deep freeze.
|
|
13
|
+
Another reason is many gems do `Ractor.make_shareable(object) if defined?(Ractor)` and so that becomes dependent on whether `Ractor` is defined before that gem loads. That problem disappears if `Ractor.make_shareable(object)` is noop.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
$ gem install ractor-shim
|
|
19
|
+
```
|
|
20
|
+
or
|
|
21
|
+
```ruby
|
|
22
|
+
# In Gemfile
|
|
23
|
+
gem "ractor-shim"
|
|
24
|
+
```
|
|
6
25
|
|
|
7
26
|
## Usage
|
|
8
27
|
|
|
9
28
|
```ruby
|
|
10
29
|
require 'ractor/shim'
|
|
30
|
+
|
|
31
|
+
Ractor.new { ... }
|
|
11
32
|
```
|
|
12
33
|
|
|
13
34
|
## Development
|
|
14
35
|
|
|
15
|
-
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `
|
|
36
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `ractor-shim.gemspec`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
|
16
37
|
|
|
17
38
|
## Contributing
|
|
18
39
|
|
data/Rakefile
CHANGED
data/lib/ractor/shim.rb
CHANGED
|
@@ -1,3 +1,424 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
builtin_ractor = !!defined?(Ractor)
|
|
4
|
+
class Ractor
|
|
5
|
+
end
|
|
6
|
+
Ractor.define_singleton_method(:builtin?) { builtin_ractor }
|
|
7
|
+
Ractor.define_singleton_method(:shim?) { !builtin_ractor }
|
|
8
|
+
|
|
9
|
+
# Ractor::Port
|
|
10
|
+
|
|
11
|
+
if Ractor.builtin?
|
|
12
|
+
class Ractor::Port
|
|
13
|
+
QUIT = Object.new.freeze
|
|
14
|
+
|
|
15
|
+
def initialize
|
|
16
|
+
@pipe = Ractor.new do
|
|
17
|
+
while true
|
|
18
|
+
msg = Ractor.receive
|
|
19
|
+
break if QUIT.equal?(msg)
|
|
20
|
+
Ractor.yield msg
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def send(message)
|
|
26
|
+
@pipe.send(message)
|
|
27
|
+
end
|
|
28
|
+
alias_method :<<, :send
|
|
29
|
+
|
|
30
|
+
def receive
|
|
31
|
+
@pipe.take
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def close
|
|
35
|
+
@pipe.send(QUIT)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def closed?
|
|
39
|
+
@pipe.inspect.end_with?(' terminated>')
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def inspect
|
|
43
|
+
super
|
|
44
|
+
end
|
|
45
|
+
end unless defined?(Ractor::Port)
|
|
46
|
+
else
|
|
47
|
+
class Ractor::Port
|
|
48
|
+
attr_reader :queue
|
|
49
|
+
private :queue
|
|
50
|
+
|
|
51
|
+
def initialize
|
|
52
|
+
@queue = Queue.new
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def send(message, move: false)
|
|
56
|
+
Ractor::SELECT_MUTEX.synchronize {
|
|
57
|
+
@queue << message
|
|
58
|
+
Ractor::SELECT_CV.broadcast
|
|
59
|
+
}
|
|
60
|
+
self
|
|
61
|
+
end
|
|
62
|
+
alias_method :<<, :send
|
|
63
|
+
|
|
64
|
+
def receive
|
|
65
|
+
@queue.pop
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def close
|
|
69
|
+
Ractor::SELECT_MUTEX.synchronize {
|
|
70
|
+
@queue.close
|
|
71
|
+
Ractor::SELECT_CV.broadcast
|
|
72
|
+
}
|
|
73
|
+
self
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def closed?
|
|
77
|
+
@queue.closed?
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def inspect
|
|
81
|
+
super
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
if Ractor.shim?
|
|
87
|
+
class Ractor
|
|
88
|
+
class Error < RuntimeError
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
class RemoteError < Error
|
|
92
|
+
attr_reader :ractor
|
|
93
|
+
def initialize(ractor)
|
|
94
|
+
@ractor = ractor
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
class ClosedError < StopIteration
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
@count = 1
|
|
102
|
+
COUNT_MUTEX = Mutex.new
|
|
103
|
+
CHANGE_COUNT = -> delta {
|
|
104
|
+
COUNT_MUTEX.synchronize { @count += delta }
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
@id = 0
|
|
108
|
+
ID_MUTEX = Mutex.new
|
|
109
|
+
GET_ID = -> {
|
|
110
|
+
ID_MUTEX.synchronize { @id += 1 }
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
SELECT_MUTEX = Mutex.new
|
|
114
|
+
SELECT_CV = ConditionVariable.new
|
|
115
|
+
|
|
116
|
+
def self.main
|
|
117
|
+
MAIN_RACTOR
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def self.main?
|
|
121
|
+
current == main
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def self.current
|
|
125
|
+
Thread.current.thread_variable_get(:current_ractor) or raise "Could not find current Ractor"
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def self.count
|
|
129
|
+
COUNT_MUTEX.synchronize { @count }
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def self.receive
|
|
133
|
+
Ractor.current.__send__(:receive)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
class << self
|
|
137
|
+
alias_method :recv, :receive
|
|
138
|
+
|
|
139
|
+
def new(...)
|
|
140
|
+
super(...)
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# def self.select_simple_spinning(*ractors)
|
|
145
|
+
# raise ArgumentError, "specify at least one ractor or `yield_value`" if ractors.empty?
|
|
146
|
+
# while true
|
|
147
|
+
# ractors.each do |ractor_or_port|
|
|
148
|
+
# if Ractor === ractor_or_port
|
|
149
|
+
# queue = ractor_or_port.out_queue
|
|
150
|
+
# elsif Ractor::Port === ractor_or_port
|
|
151
|
+
# queue = ractor_or_port.queue
|
|
152
|
+
# else
|
|
153
|
+
# raise ArgumentError, "Unexpected argument for Ractor.select: #{ractor_or_port}"
|
|
154
|
+
# end
|
|
155
|
+
#
|
|
156
|
+
# begin
|
|
157
|
+
# value = queue.pop(true)
|
|
158
|
+
# return [ractor_or_port, value]
|
|
159
|
+
# rescue ThreadError
|
|
160
|
+
# Thread.pass
|
|
161
|
+
# end
|
|
162
|
+
# end
|
|
163
|
+
# end
|
|
164
|
+
# end
|
|
165
|
+
|
|
166
|
+
def self.select(*ractors)
|
|
167
|
+
raise ArgumentError, "specify at least one ractor or `yield_value`" if ractors.empty?
|
|
168
|
+
|
|
169
|
+
SELECT_MUTEX.synchronize do
|
|
170
|
+
while true
|
|
171
|
+
ractors.each do |ractor_or_port|
|
|
172
|
+
if Ractor === ractor_or_port
|
|
173
|
+
queue = ractor_or_port.__send__(:out_queue)
|
|
174
|
+
elsif Ractor::Port === ractor_or_port
|
|
175
|
+
queue = ractor_or_port.__send__(:queue)
|
|
176
|
+
else
|
|
177
|
+
raise ArgumentError, "Unexpected argument for Ractor.select: #{ractor_or_port}"
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
begin
|
|
181
|
+
value = queue.pop(true)
|
|
182
|
+
return [ractor_or_port, value]
|
|
183
|
+
rescue ThreadError
|
|
184
|
+
# keep looping
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Wait until an item is added to a relevant Queue
|
|
189
|
+
SELECT_CV.wait(SELECT_MUTEX)
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def self.make_shareable(object, copy: false)
|
|
195
|
+
# no copy, just return the object
|
|
196
|
+
object
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def self.shareable?(object)
|
|
200
|
+
true
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def self.[](var)
|
|
204
|
+
Ractor.current[var]
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def self.[]=(var, value)
|
|
208
|
+
Ractor.current[var] = value
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def self.store_if_absent(var, &block)
|
|
212
|
+
Ractor.current.__send__(:store_if_absent, var, &block)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def self._require(feature)
|
|
216
|
+
Kernel.require(feature)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
attr_reader :name, :default_port
|
|
220
|
+
|
|
221
|
+
attr_reader :out_queue
|
|
222
|
+
private :out_queue
|
|
223
|
+
|
|
224
|
+
def initialize(*args, name: nil, &block)
|
|
225
|
+
raise ArgumentError, "must be called with a block" unless block
|
|
226
|
+
initialize_common(name, block)
|
|
227
|
+
CHANGE_COUNT.call(1)
|
|
228
|
+
|
|
229
|
+
@thread = Thread.new {
|
|
230
|
+
Thread.current.thread_variable_set(:current_ractor, self)
|
|
231
|
+
begin
|
|
232
|
+
result = self.instance_exec(*args, &block)
|
|
233
|
+
SELECT_MUTEX.synchronize {
|
|
234
|
+
unless @out_queue.closed?
|
|
235
|
+
@out_queue << result
|
|
236
|
+
SELECT_CV.broadcast
|
|
237
|
+
end
|
|
238
|
+
}
|
|
239
|
+
result
|
|
240
|
+
rescue Exception => e
|
|
241
|
+
@exception = e
|
|
242
|
+
nil
|
|
243
|
+
ensure
|
|
244
|
+
@termination_mutex.synchronize {
|
|
245
|
+
CHANGE_COUNT.call(-1)
|
|
246
|
+
@status = :terminated
|
|
247
|
+
|
|
248
|
+
monitor_message = @exception ? :aborted : :exited
|
|
249
|
+
@monitors.each { |monitor|
|
|
250
|
+
monitor << monitor_message
|
|
251
|
+
}
|
|
252
|
+
@monitors.clear
|
|
253
|
+
}
|
|
254
|
+
end
|
|
255
|
+
}
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
private def initialize_main
|
|
259
|
+
initialize_common(nil, nil)
|
|
260
|
+
@thread = nil
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
private def initialize_common(name, block)
|
|
264
|
+
@name = name.nil? ? nil : (String.try_convert(name) or raise TypeError)
|
|
265
|
+
@id = GET_ID.call
|
|
266
|
+
@status = :running
|
|
267
|
+
@from = block ? block.source_location.join(":") : nil
|
|
268
|
+
@default_port = Ractor::Port.new
|
|
269
|
+
@out_queue = Queue.new
|
|
270
|
+
@storage = {}
|
|
271
|
+
@exception = nil
|
|
272
|
+
@monitors = []
|
|
273
|
+
@termination_mutex = Mutex.new
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def [](var)
|
|
277
|
+
raise "Cannot get ractor local storage for non-current ractor" unless Ractor.current == self
|
|
278
|
+
@storage[var]
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
def []=(var, value)
|
|
282
|
+
raise "Cannot set ractor local storage for non-current ractor" unless Ractor.current == self
|
|
283
|
+
@storage[var] = value
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
private def store_if_absent(var, &block)
|
|
287
|
+
if value = @storage[var]
|
|
288
|
+
value
|
|
289
|
+
else
|
|
290
|
+
value = block.call
|
|
291
|
+
@storage[var] = value
|
|
292
|
+
value
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
def send(message, ...)
|
|
297
|
+
raise Ractor::ClosedError, "The port was already closed" if @status == :terminated || @default_port.closed?
|
|
298
|
+
@default_port.send(message, ...)
|
|
299
|
+
self
|
|
300
|
+
end
|
|
301
|
+
alias_method :<<, :send
|
|
302
|
+
|
|
303
|
+
private def receive
|
|
304
|
+
@default_port.receive
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
# def take
|
|
308
|
+
# @out_queue.pop
|
|
309
|
+
# end
|
|
310
|
+
|
|
311
|
+
private def close_incoming
|
|
312
|
+
@default_port.close
|
|
313
|
+
self
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
private def close_outgoing
|
|
317
|
+
SELECT_MUTEX.synchronize {
|
|
318
|
+
@out_queue.close
|
|
319
|
+
SELECT_CV.broadcast
|
|
320
|
+
}
|
|
321
|
+
self
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
def close
|
|
325
|
+
close_incoming
|
|
326
|
+
close_outgoing
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
def monitor(port)
|
|
330
|
+
@termination_mutex.synchronize {
|
|
331
|
+
if @status == :terminated
|
|
332
|
+
port << (@exception ? :aborted : :exited)
|
|
333
|
+
false
|
|
334
|
+
else
|
|
335
|
+
@monitors << port
|
|
336
|
+
end
|
|
337
|
+
}
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
def unmonitor(port)
|
|
341
|
+
@termination_mutex.synchronize {
|
|
342
|
+
@monitors.delete port
|
|
343
|
+
}
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
def join
|
|
347
|
+
value
|
|
348
|
+
self
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
def value
|
|
352
|
+
@thread.join
|
|
353
|
+
|
|
354
|
+
if exc = @exception
|
|
355
|
+
remote_error = RemoteError.new(self)
|
|
356
|
+
raise remote_error, cause: exc
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
@thread.value
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
def inspect
|
|
363
|
+
["#<Ractor:##{@id}", @name, @from, "#{@status}>"].compact.join(' ')
|
|
364
|
+
end
|
|
365
|
+
alias_method :to_s, :inspect
|
|
366
|
+
|
|
367
|
+
MAIN_RACTOR = Ractor.allocate
|
|
368
|
+
MAIN_RACTOR.__send__(:initialize_main)
|
|
369
|
+
Thread.main.thread_variable_set(:current_ractor, MAIN_RACTOR)
|
|
370
|
+
end
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
if Ractor.builtin?
|
|
374
|
+
class Ractor
|
|
375
|
+
unless method_defined?(:join)
|
|
376
|
+
alias_method :join, :take
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
unless method_defined?(:value)
|
|
380
|
+
alias_method :value, :take
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
unless respond_to?(:main?)
|
|
384
|
+
def self.main?
|
|
385
|
+
self == Ractor.main
|
|
386
|
+
end
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
unless method_defined?(:close)
|
|
390
|
+
def close
|
|
391
|
+
close_incoming
|
|
392
|
+
close_outgoing
|
|
393
|
+
end
|
|
394
|
+
end
|
|
395
|
+
end
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
# Ractor.{shareable_proc,shareable_lambda}
|
|
399
|
+
|
|
400
|
+
class << Ractor
|
|
401
|
+
if Ractor.builtin?
|
|
402
|
+
unless method_defined?(:shareable_proc)
|
|
403
|
+
def shareable_proc(&b)
|
|
404
|
+
Ractor.make_shareable(b)
|
|
405
|
+
end
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
unless method_defined?(:shareable_lambda)
|
|
409
|
+
def shareable_lambda(&b)
|
|
410
|
+
Ractor.make_shareable(b)
|
|
411
|
+
end
|
|
412
|
+
end
|
|
413
|
+
else
|
|
414
|
+
unless method_defined?(:shareable_proc)
|
|
415
|
+
alias_method :shareable_proc, :proc
|
|
416
|
+
public :shareable_proc
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
unless method_defined?(:shareable_lambda)
|
|
420
|
+
alias_method :shareable_lambda, :lambda
|
|
421
|
+
public :shareable_lambda
|
|
422
|
+
end
|
|
423
|
+
end
|
|
424
|
+
end
|
|
File without changes
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Based on https://github.com/truffleruby/truffleruby/blob/master/spec/truffle/methods_spec.rb
|
|
2
|
+
|
|
3
|
+
# How to regenerate files:
|
|
4
|
+
#
|
|
5
|
+
# - switch to MRI, the version we are compatible with
|
|
6
|
+
# - run `jt -u ruby test spec/truffle/methods_spec.rb`
|
|
7
|
+
|
|
8
|
+
# jt test and jt tag can be used as normal,
|
|
9
|
+
# but instead of jt untag, jt purge must be used to remove tags:
|
|
10
|
+
# $ jt purge spec/truffle/methods_spec.rb
|
|
11
|
+
|
|
12
|
+
# socket modules are found with:
|
|
13
|
+
# m1=ObjectSpace.each_object(Module).to_a; require "socket"; m2=ObjectSpace.each_object(Module).to_a; p m2-m1
|
|
14
|
+
|
|
15
|
+
modules = %w[
|
|
16
|
+
Ractor Ractor.singleton_class
|
|
17
|
+
Ractor::Port Ractor::Port.singleton_class
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
def ruby(code, *flags)
|
|
21
|
+
IO.popen([RbConfig.ruby, *flags], "r+") { |pipe|
|
|
22
|
+
pipe.write code
|
|
23
|
+
pipe.close_write
|
|
24
|
+
pipe.read
|
|
25
|
+
}
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
if RUBY_ENGINE == "ruby" && RUBY_VERSION >= "3.5"
|
|
29
|
+
modules.each do |mod|
|
|
30
|
+
file = "#{__dir__}/methods/#{mod}.txt"
|
|
31
|
+
code = "puts #{mod}.public_instance_methods(false).sort"
|
|
32
|
+
methods = ruby(code)
|
|
33
|
+
methods = methods.lines.map { |line| line.chomp.to_sym }
|
|
34
|
+
contents = methods.map { |meth| "#{meth}\n" }.join
|
|
35
|
+
File.write file, contents
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
code = <<-RUBY
|
|
40
|
+
require "ractor/shim"
|
|
41
|
+
#{modules.inspect}.each { |m|
|
|
42
|
+
puts m
|
|
43
|
+
puts eval(m).public_instance_methods(false).sort
|
|
44
|
+
puts
|
|
45
|
+
}
|
|
46
|
+
RUBY
|
|
47
|
+
all_methods = {}
|
|
48
|
+
ruby(code, "-I#{File.dirname(__dir__)}/lib").rstrip.split("\n\n").each do |group|
|
|
49
|
+
mod, *methods = group.lines.map(&:chomp)
|
|
50
|
+
all_methods[mod] = methods.map(&:to_sym)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
modules.each do |mod|
|
|
54
|
+
file = "#{__dir__}/methods/#{mod}.txt"
|
|
55
|
+
expected = File.readlines(file).map { |line| line.chomp.to_sym }
|
|
56
|
+
methods = all_methods[mod]
|
|
57
|
+
|
|
58
|
+
if methods != expected
|
|
59
|
+
extras = methods - expected
|
|
60
|
+
missing = expected - methods
|
|
61
|
+
|
|
62
|
+
if mod == "Ractor.singleton_class"
|
|
63
|
+
extras -= %i[builtin? shim?] # intended extras
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
if RUBY_ENGINE == "ruby" and RUBY_VERSION < "3.5"
|
|
67
|
+
if mod == "Ractor"
|
|
68
|
+
extras -= %i[take close_incoming close_outgoing] # expected extra on those versions
|
|
69
|
+
missing -= %i[default_port monitor unmonitor] # hard to implement on those versions
|
|
70
|
+
elsif mod == "Ractor.singleton_class"
|
|
71
|
+
extras -= %i[yield receive_if] # expected extra on those versions
|
|
72
|
+
if RUBY_VERSION < "3.4"
|
|
73
|
+
missing -= %i[[] []= store_if_absent _require] # hard to implement on those versions
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
raise "#{mod} methods should not include #{extras}" unless extras.empty?
|
|
79
|
+
raise "#{mod} methods should include #{missing}" unless missing.empty?
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
puts "#{__FILE__}: OK"
|