in_threads 1.4.0 → 1.5.0
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/.rubocop.yml +36 -24
- data/.travis.yml +9 -7
- data/CHANGELOG.markdown +82 -0
- data/LICENSE.txt +1 -1
- data/README.markdown +11 -3
- data/in_threads.gemspec +3 -3
- data/lib/in_threads.rb +152 -114
- data/spec/in_threads_spec.rb +548 -509
- metadata +7 -8
- data/spec/spec_helper.rb +0 -6
data/spec/in_threads_spec.rb
CHANGED
@@ -1,692 +1,746 @@
|
|
1
|
-
require '
|
1
|
+
require 'rspec'
|
2
|
+
require 'rspec/retry'
|
2
3
|
require 'in_threads'
|
3
4
|
|
4
|
-
|
5
|
-
|
6
|
-
# Use fast_check? for matching
|
7
|
-
module FastCheckMatcher
|
8
|
-
def self.===(item)
|
9
|
-
unless item.respond_to?(:fast_check?)
|
10
|
-
fail "#{item.inspect} doesn't respont to fast_check?"
|
11
|
-
end
|
12
|
-
item.fast_check?
|
13
|
-
end
|
14
|
-
end
|
5
|
+
RSpec.configure do |config|
|
6
|
+
config.order = :random
|
15
7
|
|
16
|
-
|
17
|
-
@i, @value = i, value
|
18
|
-
end
|
8
|
+
config.verbose_retry = true
|
19
9
|
|
20
|
-
|
21
|
-
|
10
|
+
config.around :each, :flaky do |ex|
|
11
|
+
ex.run_with_retry :retry => 3
|
22
12
|
end
|
13
|
+
end
|
23
14
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
15
|
+
# check if break causes LocalJumpError
|
16
|
+
# not in jruby in mri < 1.9
|
17
|
+
# https://github.com/jruby/jruby/issues/4697
|
18
|
+
SKIP_IF_BREAK_IN_THREAD_IS_IGNORED = begin
|
19
|
+
Thread.new{ break }.join
|
20
|
+
'can not handle break in thread'
|
21
|
+
rescue LocalJumpError
|
22
|
+
false
|
23
|
+
end
|
28
24
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
25
|
+
def describe_enum_method(method, &block)
|
26
|
+
if Enumerable.method_defined?(method) || method.to_s == 'each'
|
27
|
+
describe "##{method}", &block
|
28
|
+
else
|
29
|
+
describe "##{method}" do
|
30
|
+
let(:enum){ 10.times }
|
33
31
|
|
34
|
-
|
35
|
-
|
32
|
+
it 'is not defined' do
|
33
|
+
expect{ enum.in_threads.send(method) }.
|
34
|
+
to raise_error(NoMethodError){ |error| expect(error.name.to_s).to eq(method.to_s) }
|
35
|
+
end
|
36
|
+
end
|
36
37
|
end
|
38
|
+
end
|
37
39
|
|
38
|
-
|
39
|
-
|
40
|
-
end
|
40
|
+
class TestObject
|
41
|
+
SLEEP_TIME = 0.002
|
41
42
|
|
42
|
-
def
|
43
|
-
|
43
|
+
def initialize(value)
|
44
|
+
@value = value
|
44
45
|
end
|
45
46
|
|
46
|
-
|
47
|
-
|
48
|
-
def id
|
49
|
-
[self.class, @i, @value]
|
47
|
+
def fetch
|
48
|
+
@value
|
50
49
|
end
|
51
50
|
|
52
|
-
|
53
|
-
|
54
|
-
def sleep
|
55
|
-
Kernel.sleep 0.01
|
51
|
+
def compute
|
52
|
+
wait; @value
|
56
53
|
end
|
57
|
-
end
|
58
54
|
|
59
|
-
|
60
|
-
class RandItem < ValueItem
|
61
|
-
def initialize(i)
|
62
|
-
super(i, Kernel.rand)
|
63
|
-
end
|
55
|
+
private
|
64
56
|
|
65
|
-
def
|
66
|
-
|
57
|
+
def wait
|
58
|
+
sleep SLEEP_TIME
|
67
59
|
end
|
68
60
|
end
|
69
61
|
|
70
|
-
|
71
|
-
|
72
|
-
include Enumerable
|
73
|
-
|
74
|
-
def initialize(count, &block)
|
75
|
-
@items = Array.new(count){ |i| block[i] }
|
76
|
-
end
|
62
|
+
describe InThreads do
|
63
|
+
let!(:mutex){ Mutex.new }
|
77
64
|
|
78
|
-
|
79
|
-
|
80
|
-
|
65
|
+
# Check if all threads are joined
|
66
|
+
let!(:threads_before){ Thread.list }
|
67
|
+
after do
|
68
|
+
threads = (Thread.list - threads_before).reject do |thread|
|
69
|
+
thread.respond_to?(:backtrace) && thread.backtrace.none?{ |l| l =~ /in_threads/ }
|
70
|
+
end
|
71
|
+
expect(threads).to eq([]), 'expected all created threads to be joined'
|
81
72
|
end
|
82
|
-
end
|
83
73
|
|
84
|
-
|
74
|
+
describe 'initialization' do
|
75
|
+
it 'complains about using with non enumerable' do
|
76
|
+
expect{ InThreads.new(1) }.to raise_error(ArgumentError)
|
77
|
+
end
|
85
78
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
describe "##{method}", &block
|
90
|
-
else
|
91
|
-
describe "##{method}" do
|
92
|
-
it 'is not defined' do
|
93
|
-
exception_regexp =
|
94
|
-
/^undefined method `#{Regexp.escape(method)}' .*\bInThreads\b/
|
95
|
-
expect{ enum.in_threads.send(method) }.
|
96
|
-
to raise_error(NoMethodError, exception_regexp)
|
79
|
+
[1..10, 10.times, {}, []].each do |o|
|
80
|
+
it "does not complain about using with #{o.class}" do
|
81
|
+
expect{ InThreads.new(o) }.not_to raise_error
|
97
82
|
end
|
98
83
|
end
|
99
|
-
end
|
100
|
-
end
|
101
84
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
# small coefficient, should be more if sleep time coefficient is bigger
|
107
|
-
let(:speed_coef){ 0.666 }
|
85
|
+
it 'complains about using less than 2 threads' do
|
86
|
+
expect{ InThreads.new(10.times, 1) }.to raise_error(ArgumentError)
|
87
|
+
end
|
108
88
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
Time.now - start
|
89
|
+
it 'does not complain about using 2 or more threads' do
|
90
|
+
expect{ InThreads.new(10.times, 2) }.not_to raise_error
|
91
|
+
end
|
113
92
|
end
|
114
93
|
|
115
|
-
describe '
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
end
|
94
|
+
describe '#in_threads' do
|
95
|
+
context 'when applied to an instance of InThreads' do
|
96
|
+
let(:threaded){ 10.times.in_threads(10) }
|
97
|
+
let(:double_threaded){ threaded.in_threads(20) }
|
120
98
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
end
|
99
|
+
it 'creates new instance' do
|
100
|
+
expect(double_threaded.class).to eq(threaded.class)
|
101
|
+
expect(double_threaded.object_id).not_to eq(threaded.object_id)
|
125
102
|
end
|
126
103
|
|
127
|
-
it '
|
128
|
-
expect
|
104
|
+
it 'changes the maximum thread count' do
|
105
|
+
expect(threaded.thread_count).to eq(10)
|
106
|
+
expect(double_threaded.thread_count).to eq(20)
|
129
107
|
end
|
130
108
|
|
131
|
-
it '
|
132
|
-
expect
|
109
|
+
it 'preserves the enumerable' do
|
110
|
+
expect(threaded.enumerable).to be(double_threaded.enumerable)
|
133
111
|
end
|
134
112
|
end
|
113
|
+
end
|
135
114
|
|
136
|
-
|
137
|
-
|
138
|
-
threaded = enum.in_threads(10)
|
139
|
-
expect{ threaded.in_threads(20) }.not_to change(threaded, :thread_count)
|
140
|
-
end
|
141
|
-
|
142
|
-
it 'creates new instance with different title when called on '\
|
143
|
-
'WithProgress' do
|
144
|
-
threaded = enum.in_threads(10)
|
145
|
-
tthreaded = threaded.in_threads(20)
|
146
|
-
expect(threaded.thread_count).to eq(10)
|
147
|
-
expect(tthreaded.thread_count).to eq(20)
|
148
|
-
expect(tthreaded.class).to eq(threaded.class)
|
149
|
-
expect(tthreaded.object_id).not_to eq(threaded.object_id)
|
150
|
-
expect(tthreaded.enumerable).to eq(threaded.enumerable)
|
151
|
-
end
|
152
|
-
end
|
115
|
+
describe 'consistency' do
|
116
|
+
let(:enum){ 100.times }
|
153
117
|
|
154
|
-
describe '
|
155
|
-
let(:
|
118
|
+
describe 'runs in specified number of threads' do
|
119
|
+
let(:enum){ 40.times }
|
120
|
+
let(:threads){ 4 }
|
156
121
|
|
157
122
|
%w[each map all?].each do |method|
|
158
|
-
it "
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
@max_thread_count = [@max_thread_count, @thread_count].max
|
123
|
+
it "for ##{method}", :flaky do
|
124
|
+
thread_count = 0
|
125
|
+
max_thread_count = 0
|
126
|
+
enum.in_threads(threads).send(method) do
|
127
|
+
mutex.synchronize do
|
128
|
+
thread_count += 1
|
129
|
+
max_thread_count = [max_thread_count, thread_count].max
|
166
130
|
end
|
167
|
-
|
168
|
-
|
169
|
-
|
131
|
+
sleep TestObject::SLEEP_TIME
|
132
|
+
mutex.synchronize do
|
133
|
+
thread_count -= 1
|
170
134
|
end
|
171
|
-
res
|
172
135
|
end
|
173
|
-
expect(
|
174
|
-
expect(
|
136
|
+
expect(thread_count).to eq(0)
|
137
|
+
expect(max_thread_count).to eq(threads)
|
175
138
|
end
|
176
139
|
end
|
177
140
|
end
|
178
141
|
|
179
|
-
describe '
|
142
|
+
describe 'exception/break handling' do
|
180
143
|
%w[each map all?].each do |method|
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
enum.in_threads(13).send(method, &:check?)
|
186
|
-
end
|
187
|
-
end
|
144
|
+
describe "for ##{method}" do
|
145
|
+
it 'passes exception raised in block' do
|
146
|
+
expect{ enum.in_threads.send(method){ fail 'expected' } }.to raise_error('expected')
|
147
|
+
end
|
188
148
|
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
fail
|
194
|
-
end
|
149
|
+
it 'passes exception raised during iteration' do
|
150
|
+
def enum.each
|
151
|
+
fail 'expected'
|
152
|
+
end
|
195
153
|
|
196
|
-
|
197
|
-
|
198
|
-
end
|
154
|
+
expect{ enum.in_threads.send(method){} }.to raise_error('expected')
|
155
|
+
end
|
199
156
|
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
yield 4, 5, 6
|
207
|
-
end
|
208
|
-
end
|
157
|
+
it 'handles break', :skip => SKIP_IF_BREAK_IN_THREAD_IS_IGNORED do
|
158
|
+
expect(enum).not_to receive(:unexpected)
|
159
|
+
def enum.each(&block)
|
160
|
+
20.times(&block)
|
161
|
+
unexpected
|
162
|
+
end
|
209
163
|
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
enum.in_threads.each do |*args|
|
216
|
-
o.notify(args)
|
217
|
-
end
|
218
|
-
end
|
164
|
+
value = double
|
165
|
+
expect(enum.in_threads(10).send(method) do
|
166
|
+
break value
|
167
|
+
end).to eq(value)
|
168
|
+
end
|
219
169
|
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
o.notify(args)
|
227
|
-
end
|
228
|
-
end
|
229
|
-
end
|
230
|
-
end
|
170
|
+
it 'stops iterating after exception' do
|
171
|
+
expect(enum).not_to receive(:unexpected)
|
172
|
+
def enum.each(&block)
|
173
|
+
20.times(&block)
|
174
|
+
unexpected
|
175
|
+
end
|
231
176
|
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
177
|
+
expect do
|
178
|
+
enum.in_threads(10).send(method) do |i|
|
179
|
+
fail 'expected' if i == 5
|
180
|
+
sleep TestObject::SLEEP_TIME
|
181
|
+
end
|
182
|
+
end.to raise_error('expected')
|
183
|
+
end
|
238
184
|
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
end
|
185
|
+
it 'finishes blocks started before exception' do
|
186
|
+
started = []
|
187
|
+
finished = []
|
243
188
|
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
189
|
+
expect do
|
190
|
+
enum.in_threads(10).send(method) do |i|
|
191
|
+
fail 'expected' if i == 5
|
192
|
+
mutex.synchronize{ started << i }
|
193
|
+
sleep TestObject::SLEEP_TIME
|
194
|
+
mutex.synchronize{ finished << i }
|
195
|
+
end
|
196
|
+
end.to raise_error('expected')
|
248
197
|
|
249
|
-
|
250
|
-
|
251
|
-
enum.in_threads.each(&:touch_n_value)
|
252
|
-
end
|
198
|
+
expect(finished).to match_array(started)
|
199
|
+
end
|
253
200
|
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
201
|
+
context 'exception order' do
|
202
|
+
before do
|
203
|
+
stub_const('Order', Queue.new)
|
204
|
+
end
|
258
205
|
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
206
|
+
it 'passes exception raised during iteration if it happens earlier than in block' do
|
207
|
+
def enum.each(&block)
|
208
|
+
5.times(&block)
|
209
|
+
begin
|
210
|
+
fail 'expected'
|
211
|
+
ensure
|
212
|
+
Order.push nil
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
expect do
|
217
|
+
enum.in_threads(10).send(method) do
|
218
|
+
Thread.pass while Order.empty?
|
219
|
+
sleep TestObject::SLEEP_TIME
|
220
|
+
fail 'unexpected'
|
221
|
+
end
|
222
|
+
end.to raise_error('expected')
|
223
|
+
end
|
263
224
|
|
264
|
-
|
265
|
-
|
266
|
-
|
225
|
+
it 'passes exception raised in block if it happens earlier than during iteration' do
|
226
|
+
def enum.each(&block)
|
227
|
+
5.times(&block)
|
228
|
+
Thread.pass while Order.empty?
|
229
|
+
sleep TestObject::SLEEP_TIME
|
230
|
+
fail 'unexpected'
|
231
|
+
end
|
232
|
+
|
233
|
+
expect do
|
234
|
+
enum.in_threads(10).send(method) do
|
235
|
+
begin
|
236
|
+
fail 'expected'
|
237
|
+
ensure
|
238
|
+
Order.push nil
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end.to raise_error('expected')
|
242
|
+
end
|
267
243
|
|
268
|
-
|
269
|
-
|
270
|
-
|
244
|
+
it 'passes first exception raised in block' do
|
245
|
+
expect do
|
246
|
+
enum.in_threads(10).send(method) do |i|
|
247
|
+
if i == 5
|
248
|
+
begin
|
249
|
+
fail 'expected'
|
250
|
+
ensure
|
251
|
+
Order.push nil
|
252
|
+
end
|
253
|
+
else
|
254
|
+
Thread.pass while Order.empty?
|
255
|
+
sleep TestObject::SLEEP_TIME
|
256
|
+
fail 'unexpected'
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end.to raise_error('expected')
|
260
|
+
end
|
261
|
+
end
|
271
262
|
end
|
272
263
|
end
|
273
264
|
end
|
274
265
|
|
275
|
-
|
276
|
-
|
277
|
-
let(:runner){ proc{ |o, _i| o.value } }
|
266
|
+
it 'does not yield all elements when not needed' do
|
267
|
+
expect(enum).not_to receive(:unexpected)
|
278
268
|
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
269
|
+
def enum.each(&block)
|
270
|
+
100.times(&block)
|
271
|
+
unexpected
|
272
|
+
end
|
283
273
|
|
284
|
-
|
285
|
-
|
286
|
-
enum.in_threads.send(method){ |o, i| o.touch_n_value(i) }
|
287
|
-
end
|
274
|
+
enum.in_threads(10).all?{ false }
|
275
|
+
end
|
288
276
|
|
289
|
-
|
290
|
-
|
291
|
-
|
277
|
+
describe 'calls underlying enumerable #each only once' do
|
278
|
+
%w[each map all?].each do |method|
|
279
|
+
it "for ##{method}" do
|
280
|
+
expect(enum).to receive(:each).once.and_call_original
|
281
|
+
enum.in_threads.send(method){ sleep TestObject::SLEEP_TIME }
|
292
282
|
end
|
283
|
+
end
|
284
|
+
end
|
293
285
|
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
286
|
+
describe 'block arguments' do
|
287
|
+
%w[each map all? each_entry each_with_index].each do |method|
|
288
|
+
describe_enum_method method do
|
289
|
+
it 'passes arguments as for not threaded call' do
|
290
|
+
enum = Class.new do
|
291
|
+
include Enumerable
|
292
|
+
|
293
|
+
def each
|
294
|
+
yield
|
295
|
+
yield 1
|
296
|
+
yield 2, 3
|
297
|
+
yield 4, 5, 6
|
298
|
+
end
|
299
|
+
end.new
|
300
|
+
|
301
|
+
expected = []
|
302
|
+
enum.send(method) do |a, b, c|
|
303
|
+
expected << [a, b, c]
|
304
|
+
end
|
298
305
|
|
299
|
-
|
300
|
-
|
301
|
-
|
306
|
+
yielded = []
|
307
|
+
enum.in_threads.send(method) do |a, b, c|
|
308
|
+
mutex.synchronize{ yielded << [a, b, c] }
|
309
|
+
end
|
310
|
+
|
311
|
+
expect(yielded).to match_array(expected)
|
302
312
|
end
|
303
313
|
end
|
304
314
|
end
|
305
315
|
end
|
316
|
+
end
|
306
317
|
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
to eq(enum.reverse_each(&:value))
|
311
|
-
end
|
318
|
+
describe 'methods' do
|
319
|
+
define :be_faster_than do
|
320
|
+
coef = 0.666 # small coefficient, should be more if sleep time is bigger
|
312
321
|
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
expect(@order).to receive(:notify).with(items.first).ordered
|
318
|
-
enum.each{ |o| expect(o).to receive(:touch).once }
|
319
|
-
@mutex = Mutex.new
|
320
|
-
enum.in_threads.reverse_each do |o|
|
321
|
-
@mutex.synchronize{ @order.notify(o) }
|
322
|
-
o.touch_n_value
|
323
|
-
end
|
322
|
+
def measure
|
323
|
+
start = Time.now
|
324
|
+
yield
|
325
|
+
Time.now - start
|
324
326
|
end
|
325
327
|
|
326
|
-
|
327
|
-
|
328
|
-
to be < measure{ enum.reverse_each(&:value) } * speed_coef
|
328
|
+
match do |actual|
|
329
|
+
measure(&actual) < measure(&block_arg) * coef
|
329
330
|
end
|
330
331
|
|
331
|
-
|
332
|
-
expect(enum.in_threads.reverse_each.to_a).to eq(enum.reverse_each.to_a)
|
333
|
-
end
|
332
|
+
failure_message{ "expected to be faster (coef. #{coef})" }
|
334
333
|
|
335
|
-
|
336
|
-
check_test_exception(enum) do |threaded|
|
337
|
-
threaded.reverse_each{ fail TestException }
|
338
|
-
end
|
339
|
-
end
|
334
|
+
supports_block_expectations
|
340
335
|
end
|
341
336
|
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
337
|
+
it 'lists all incompatible methods' do
|
338
|
+
expect(InThreads::INCOMPATIBLE_METHODS.sort_by(&:to_s)).
|
339
|
+
to include(*(
|
340
|
+
Enumerable.instance_methods.map(&:to_sym) -
|
341
|
+
InThreads.public_instance_methods(false).map(&:to_sym)
|
342
|
+
).sort_by(&:to_s))
|
343
|
+
end
|
348
344
|
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
345
|
+
context 'threaded' do
|
346
|
+
let(:item_count){ 40 }
|
347
|
+
let(:value_proc){ proc{ rand } }
|
348
|
+
let(:items){ Array.new(item_count){ |i| TestObject.new(value_proc[i]) } }
|
349
|
+
let(:enum){ items }
|
353
350
|
|
354
|
-
|
355
|
-
|
356
|
-
enum.
|
357
|
-
|
358
|
-
o.check?
|
359
|
-
end
|
351
|
+
describe_enum_method :each do
|
352
|
+
it 'returns same enum after running' do
|
353
|
+
expect(enum.in_threads.each(&:compute)).to eq(enum)
|
354
|
+
end
|
360
355
|
|
361
|
-
|
362
|
-
|
363
|
-
enum.in_threads.
|
364
|
-
|
365
|
-
o.check?
|
356
|
+
it 'executes block for each element' do
|
357
|
+
yielded = []
|
358
|
+
enum.in_threads.each do |item|
|
359
|
+
mutex.synchronize{ yielded << item }
|
366
360
|
end
|
361
|
+
expect(yielded).to match_array(items)
|
362
|
+
end
|
367
363
|
|
368
|
-
|
369
|
-
expect(
|
364
|
+
it 'runs faster with threads', :flaky do
|
365
|
+
expect{ enum.in_threads.each(&:compute) }.
|
366
|
+
to be_faster_than{ enum.each(&:compute) }
|
370
367
|
end
|
371
368
|
|
372
|
-
it 'runs faster with threads', :
|
373
|
-
|
374
|
-
|
375
|
-
expect(measure{ enum.in_threads.send(method, &:check?) }).
|
376
|
-
to be < measure{ enum.send(method, &:check?) } * speed_coef
|
369
|
+
it 'runs faster with more threads', :flaky do
|
370
|
+
expect{ enum.in_threads(10).each(&:compute) }.
|
371
|
+
to be_faster_than{ enum.in_threads(2).each(&:compute) }
|
377
372
|
end
|
378
373
|
|
379
|
-
it '
|
380
|
-
|
381
|
-
threaded.send(method){ fail TestException }
|
382
|
-
end
|
374
|
+
it 'returns same enum without block' do
|
375
|
+
expect(enum.in_threads.each.to_a).to eq(enum.each.to_a)
|
383
376
|
end
|
384
377
|
end
|
385
|
-
end
|
386
378
|
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
expect(enum.in_threads.send(method, &:check?)).
|
391
|
-
to eq(enum.send(method, &:check?))
|
392
|
-
end
|
379
|
+
%w[each_with_index enum_with_index].each do |method|
|
380
|
+
describe_enum_method method do
|
381
|
+
let(:block){ proc{ |o, _i| o.compute } }
|
393
382
|
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
383
|
+
it 'returns same result with threads' do
|
384
|
+
expect(enum.in_threads.send(method, &block)).
|
385
|
+
to eq(enum.send(method, &block))
|
386
|
+
end
|
398
387
|
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
388
|
+
it 'yields same objects' do
|
389
|
+
yielded = []
|
390
|
+
enum.in_threads.send(method) do |o, i|
|
391
|
+
mutex.synchronize{ yielded << [o, i] }
|
392
|
+
end
|
393
|
+
expect(yielded).to match_array(enum.send(method))
|
394
|
+
end
|
403
395
|
|
404
|
-
|
405
|
-
|
406
|
-
|
396
|
+
it 'runs faster with threads', :flaky do
|
397
|
+
expect{ enum.in_threads.send(method, &block) }.
|
398
|
+
to be_faster_than{ enum.send(method, &block) }
|
399
|
+
end
|
400
|
+
|
401
|
+
it 'returns same enum without block' do
|
402
|
+
expect(enum.in_threads.send(method).to_a).
|
403
|
+
to eq(enum.send(method).to_a)
|
407
404
|
end
|
408
405
|
end
|
409
406
|
end
|
410
|
-
end
|
411
407
|
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
sum uniq
|
416
|
-
].each do |method|
|
417
|
-
describe_enum_method method do
|
408
|
+
describe_enum_method :reverse_each do
|
409
|
+
let(:item_count){ 100 }
|
410
|
+
|
418
411
|
it 'returns same result with threads' do
|
419
|
-
expect(enum.in_threads.
|
420
|
-
to eq(enum.
|
412
|
+
expect(enum.in_threads.reverse_each(&:compute)).
|
413
|
+
to eq(enum.reverse_each(&:compute))
|
421
414
|
end
|
422
415
|
|
423
|
-
it '
|
424
|
-
|
425
|
-
enum.in_threads.
|
416
|
+
it 'yields same objects in reverse order' do
|
417
|
+
yielded = []
|
418
|
+
enum.in_threads.reverse_each do |o|
|
419
|
+
mutex.synchronize{ yielded << o }
|
420
|
+
end
|
421
|
+
|
422
|
+
expect(yielded).to match_array(items)
|
423
|
+
expect(yielded.index(items.last)).
|
424
|
+
to be < yielded.index(items[items.length / 4])
|
425
|
+
expect(yielded.index(items.first)).
|
426
|
+
to be > yielded.index(items[-items.length / 4])
|
426
427
|
end
|
427
428
|
|
428
|
-
it 'runs faster with threads', :
|
429
|
-
expect
|
430
|
-
to
|
429
|
+
it 'runs faster with threads', :flaky do
|
430
|
+
expect{ enum.in_threads.reverse_each(&:compute) }.
|
431
|
+
to be_faster_than{ enum.reverse_each(&:compute) }
|
431
432
|
end
|
432
433
|
|
433
|
-
it '
|
434
|
-
|
435
|
-
|
436
|
-
end
|
434
|
+
it 'returns same enum without block' do
|
435
|
+
expect(enum.in_threads.reverse_each.to_a).
|
436
|
+
to eq(enum.reverse_each.to_a)
|
437
437
|
end
|
438
438
|
end
|
439
|
-
end
|
440
439
|
|
441
|
-
|
442
|
-
|
443
|
-
|
440
|
+
%w[
|
441
|
+
all? any? none? one?
|
442
|
+
detect find find_index drop_while take_while
|
443
|
+
].each do |method|
|
444
|
+
describe_enum_method method do
|
445
|
+
let(:value_proc){ proc{ |i| i.odd? } }
|
444
446
|
|
445
|
-
|
446
|
-
|
447
|
-
|
447
|
+
it 'returns same result with threads' do
|
448
|
+
expect(enum.in_threads.send(method, &:compute)).
|
449
|
+
to eq(enum.send(method, &:compute))
|
448
450
|
end
|
449
|
-
enum.in_threads.send(method, 3){ |a| a.first.touch_n_value(a) }
|
450
|
-
end
|
451
451
|
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
452
|
+
it 'yields same objects but not all' do
|
453
|
+
expected = []
|
454
|
+
enum.send(method) do |o|
|
455
|
+
expected << o
|
456
|
+
o.compute
|
457
|
+
end
|
456
458
|
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
459
|
+
yielded = []
|
460
|
+
enum.in_threads.send(method) do |o|
|
461
|
+
mutex.synchronize{ yielded << o }
|
462
|
+
o.compute
|
463
|
+
end
|
461
464
|
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
end
|
465
|
+
expect(yielded.length).to be >= expected.length
|
466
|
+
expect(yielded.length).to be <= items.length * 0.5
|
467
|
+
end
|
466
468
|
|
467
|
-
|
468
|
-
|
469
|
-
|
469
|
+
context 'speed' do
|
470
|
+
let(:value_proc) do
|
471
|
+
proc{ %w[all? drop_while take_while].include?(method) }
|
472
|
+
end
|
473
|
+
|
474
|
+
it 'runs faster with threads', :flaky do
|
475
|
+
expect{ enum.in_threads.send(method, &:compute) }.
|
476
|
+
to be_faster_than{ enum.send(method, &:compute) }
|
477
|
+
end
|
470
478
|
end
|
471
479
|
end
|
472
480
|
end
|
473
|
-
end
|
474
481
|
|
475
|
-
|
476
|
-
|
482
|
+
%w[partition find_all select reject count].each do |method|
|
483
|
+
describe_enum_method method do
|
484
|
+
let(:value_proc){ proc{ rand < 0.5 } }
|
477
485
|
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
486
|
+
it 'returns same result with threads' do
|
487
|
+
expect(enum.in_threads.send(method, &:compute)).
|
488
|
+
to eq(enum.send(method, &:compute))
|
489
|
+
end
|
482
490
|
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
491
|
+
it 'yields same objects' do
|
492
|
+
yielded = []
|
493
|
+
enum.in_threads.send(method) do |o|
|
494
|
+
mutex.synchronize{ yielded << o }
|
495
|
+
end
|
496
|
+
expect(yielded).to match_array(items)
|
497
|
+
end
|
487
498
|
|
488
|
-
|
489
|
-
|
490
|
-
|
499
|
+
it 'runs faster with threads', :flaky do
|
500
|
+
expect{ enum.in_threads.send(method, &:compute) }.
|
501
|
+
to be_faster_than{ enum.send(method, &:compute) }
|
502
|
+
end
|
503
|
+
end
|
491
504
|
end
|
492
505
|
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
506
|
+
%w[
|
507
|
+
collect map
|
508
|
+
group_by max_by min_by minmax_by sort_by
|
509
|
+
sum uniq
|
510
|
+
].each do |method|
|
511
|
+
describe_enum_method method do
|
512
|
+
it 'returns same result with threads' do
|
513
|
+
expect(enum.in_threads.send(method, &:compute)).
|
514
|
+
to eq(enum.send(method, &:compute))
|
515
|
+
end
|
497
516
|
|
498
|
-
|
499
|
-
|
500
|
-
|
517
|
+
it 'yields same objects' do
|
518
|
+
yielded = []
|
519
|
+
enum.in_threads.send(method) do |o|
|
520
|
+
mutex.synchronize{ yielded << o }
|
521
|
+
o.compute
|
522
|
+
end
|
523
|
+
expect(yielded).to match_array(items)
|
524
|
+
end
|
501
525
|
|
502
|
-
|
503
|
-
|
504
|
-
|
526
|
+
it 'runs faster with threads', :flaky do
|
527
|
+
expect{ enum.in_threads.send(method, &:compute) }.
|
528
|
+
to be_faster_than{ enum.send(method, &:compute) }
|
529
|
+
end
|
505
530
|
end
|
506
531
|
end
|
507
|
-
end
|
508
532
|
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
enum.in_threads.cycle(3, &:touch_n_value)
|
513
|
-
end
|
533
|
+
%w[each_cons each_slice enum_slice enum_cons].each do |method|
|
534
|
+
describe_enum_method method do
|
535
|
+
let(:block){ proc{ |a| a.each(&:compute) } }
|
514
536
|
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
537
|
+
it 'yields same objects' do
|
538
|
+
yielded = []
|
539
|
+
enum.in_threads.send(method, 3) do |a|
|
540
|
+
mutex.synchronize{ yielded << a }
|
541
|
+
end
|
542
|
+
expect(yielded).to match_array(items.send(method, 3))
|
543
|
+
end
|
519
544
|
|
520
|
-
|
521
|
-
|
522
|
-
|
545
|
+
it 'returns same with block' do
|
546
|
+
expect(enum.in_threads.send(method, 3, &block)).
|
547
|
+
to eq(enum.send(method, 3, &block))
|
548
|
+
end
|
549
|
+
|
550
|
+
it 'runs faster with threads', :flaky do
|
551
|
+
expect{ enum.in_threads.send(method, 3, &block) }.
|
552
|
+
to be_faster_than{ enum.send(method, 3, &block) }
|
553
|
+
end
|
523
554
|
|
524
|
-
|
525
|
-
|
526
|
-
|
555
|
+
it 'returns same without block' do
|
556
|
+
expect(enum.in_threads.send(method, 3).to_a).
|
557
|
+
to eq(enum.send(method, 3).to_a)
|
558
|
+
end
|
527
559
|
end
|
528
560
|
end
|
529
|
-
end
|
530
561
|
|
531
|
-
|
532
|
-
|
533
|
-
let(:matcher){ ValueItem::FastCheckMatcher }
|
562
|
+
describe_enum_method :zip do
|
563
|
+
let(:block){ proc{ |a| a.each(&:compute) } }
|
534
564
|
|
535
|
-
it '
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
else
|
540
|
-
expect(o).not_to receive(:touch)
|
541
|
-
end
|
565
|
+
it 'yields same objects' do
|
566
|
+
yielded = []
|
567
|
+
enum.in_threads.zip(enum, enum) do |a|
|
568
|
+
mutex.synchronize{ yielded << a }
|
542
569
|
end
|
543
|
-
enum.
|
570
|
+
expect(yielded).to match_array(enum.zip(enum, enum))
|
544
571
|
end
|
545
572
|
|
546
573
|
it 'returns same with block' do
|
547
|
-
expect(enum.in_threads.
|
548
|
-
to eq(enum.
|
574
|
+
expect(enum.in_threads.zip(enum, enum, &block)).
|
575
|
+
to eq(enum.zip(enum, enum, &block))
|
549
576
|
end
|
550
577
|
|
551
|
-
it 'runs faster with threads', :
|
552
|
-
expect
|
553
|
-
to
|
578
|
+
it 'runs faster with threads', :flaky do
|
579
|
+
expect{ enum.in_threads.zip(enum, enum, &block) }.
|
580
|
+
to be_faster_than{ enum.zip(enum, enum, &block) }
|
554
581
|
end
|
555
582
|
|
556
583
|
it 'returns same without block' do
|
557
|
-
expect(enum.in_threads.
|
558
|
-
to eq(enum.
|
584
|
+
expect(enum.in_threads.zip(enum, enum)).
|
585
|
+
to eq(enum.zip(enum, enum))
|
559
586
|
end
|
587
|
+
end
|
560
588
|
|
561
|
-
|
562
|
-
|
563
|
-
|
589
|
+
describe_enum_method :cycle do
|
590
|
+
it 'yields same objects' do
|
591
|
+
yielded = []
|
592
|
+
enum.in_threads.cycle(3) do |o|
|
593
|
+
mutex.synchronize{ yielded << o }
|
564
594
|
end
|
595
|
+
expect(yielded).to match_array(enum.cycle(3))
|
565
596
|
end
|
566
|
-
end
|
567
|
-
end
|
568
597
|
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
10.times{ yield }
|
573
|
-
10.times{ yield 1 }
|
574
|
-
10.times{ yield 2, 3 }
|
575
|
-
10.times{ yield 4, 5, 6 }
|
598
|
+
it 'runs faster with threads', :flaky do
|
599
|
+
expect{ enum.in_threads.cycle(3, &:compute) }.
|
600
|
+
to be_faster_than{ enum.cycle(3, &:compute) }
|
576
601
|
end
|
577
|
-
end
|
578
|
-
let(:runner){ proc{ |o| ValueItem.new(0, o).value } }
|
579
602
|
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
end
|
584
|
-
|
585
|
-
it 'executes block for each element' do
|
586
|
-
@o = double('order')
|
587
|
-
enum.each_entry do |*o|
|
588
|
-
expect(@o).to receive(:notify).with(o)
|
589
|
-
end
|
590
|
-
@mutex = Mutex.new
|
591
|
-
enum.in_threads.each_entry do |*o|
|
592
|
-
@mutex.synchronize{ @o.notify(o) }
|
593
|
-
runner[]
|
603
|
+
it 'returns same enum without block' do
|
604
|
+
expect(enum.in_threads.cycle(3).to_a).
|
605
|
+
to eq(enum.cycle(3).to_a)
|
594
606
|
end
|
595
607
|
end
|
596
608
|
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
end
|
609
|
+
%w[grep grep_v].each do |method|
|
610
|
+
describe_enum_method method do
|
611
|
+
let(:value_proc){ proc{ rand < 0.5 } }
|
601
612
|
|
602
|
-
|
603
|
-
|
604
|
-
|
613
|
+
let(:matcher) do
|
614
|
+
double.tap do |matcher|
|
615
|
+
def matcher.===(item)
|
616
|
+
item.fetch
|
617
|
+
end
|
618
|
+
end
|
619
|
+
end
|
620
|
+
|
621
|
+
it 'yields same objects' do
|
622
|
+
yielded = []
|
623
|
+
enum.in_threads.send(method, matcher) do |item|
|
624
|
+
mutex.synchronize{ yielded << item }
|
625
|
+
end
|
626
|
+
expect(yielded).to match_array(enum.send(method, matcher))
|
627
|
+
end
|
628
|
+
|
629
|
+
it 'returns same with block' do
|
630
|
+
expect(enum.in_threads.send(method, matcher, &:compute)).
|
631
|
+
to eq(enum.send(method, matcher, &:compute))
|
632
|
+
end
|
633
|
+
|
634
|
+
it 'runs faster with threads', :flaky do
|
635
|
+
expect{ enum.in_threads.send(method, matcher, &:compute) }.
|
636
|
+
to be_faster_than{ enum.send(method, matcher, &:compute) }
|
637
|
+
end
|
605
638
|
|
606
|
-
|
607
|
-
|
608
|
-
|
639
|
+
it 'returns same without block' do
|
640
|
+
expect(enum.in_threads.send(method, matcher)).
|
641
|
+
to eq(enum.send(method, matcher))
|
642
|
+
end
|
609
643
|
end
|
610
644
|
end
|
611
|
-
end
|
612
645
|
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
646
|
+
describe_enum_method :each_entry do
|
647
|
+
before do
|
648
|
+
def enum.each
|
649
|
+
(count / 3).times do
|
650
|
+
yield
|
651
|
+
yield 1
|
652
|
+
yield 2, 3
|
653
|
+
end
|
654
|
+
end
|
655
|
+
end
|
656
|
+
let(:block){ proc{ |o| TestObject.new(o).compute } }
|
617
657
|
|
618
658
|
it 'returns same result with threads' do
|
619
|
-
expect(enum.in_threads.
|
620
|
-
to eq(enum.
|
659
|
+
expect(enum.in_threads.each_entry(&block)).
|
660
|
+
to eq(enum.each_entry(&block))
|
621
661
|
end
|
622
662
|
|
623
|
-
it '
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
end
|
663
|
+
it 'executes block for each element' do
|
664
|
+
expected = []
|
665
|
+
enum.each_entry do |*o|
|
666
|
+
expected << o
|
628
667
|
end
|
629
668
|
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
end
|
669
|
+
yielded = []
|
670
|
+
enum.in_threads.each_entry do |*o|
|
671
|
+
mutex.synchronize{ yielded << o }
|
634
672
|
end
|
673
|
+
|
674
|
+
expect(yielded).to match_array(expected)
|
635
675
|
end
|
636
676
|
|
637
|
-
it 'runs faster with threads', :
|
638
|
-
expect
|
639
|
-
to
|
677
|
+
it 'runs faster with threads', :flaky do
|
678
|
+
expect{ enum.in_threads.each_entry(&block) }.
|
679
|
+
to be_faster_than{ enum.each_entry(&block) }
|
640
680
|
end
|
641
681
|
|
642
682
|
it 'returns same enum without block' do
|
643
|
-
expect(enum.in_threads.
|
644
|
-
to eq(enum.
|
683
|
+
expect(enum.in_threads.each_entry.to_a).
|
684
|
+
to eq(enum.each_entry.to_a)
|
645
685
|
end
|
686
|
+
end
|
687
|
+
|
688
|
+
%w[flat_map collect_concat].each do |method|
|
689
|
+
describe_enum_method method do
|
690
|
+
let(:items){ super().each_slice(3).to_a }
|
691
|
+
let(:block){ proc{ |a| a.map(&:compute) } }
|
646
692
|
|
647
|
-
|
648
|
-
|
649
|
-
|
693
|
+
it 'returns same result with threads' do
|
694
|
+
expect(enum.in_threads.send(method, &block)).
|
695
|
+
to eq(enum.send(method, &block))
|
696
|
+
end
|
697
|
+
|
698
|
+
it 'yields same objects' do
|
699
|
+
yielded = []
|
700
|
+
enum.in_threads.send(method) do |a|
|
701
|
+
mutex.synchronize{ yielded << a }
|
702
|
+
end
|
703
|
+
expect(yielded).to match_array(items)
|
704
|
+
end
|
705
|
+
|
706
|
+
it 'runs faster with threads', :flaky do
|
707
|
+
expect{ enum.in_threads.send(method, &block) }.
|
708
|
+
to be_faster_than{ enum.send(method, &block) }
|
709
|
+
end
|
710
|
+
|
711
|
+
it 'returns same enum without block' do
|
712
|
+
expect(enum.in_threads.send(method).to_a).
|
713
|
+
to eq(enum.send(method).to_a)
|
650
714
|
end
|
651
715
|
end
|
652
716
|
end
|
653
717
|
end
|
654
718
|
|
655
719
|
context 'unthreaded' do
|
720
|
+
let(:enum){ (1..10).each }
|
721
|
+
|
656
722
|
%w[inject reduce].each do |method|
|
657
|
-
|
723
|
+
describe_enum_method method do
|
658
724
|
it 'returns same result' do
|
659
|
-
combiner = proc{ |memo, o| memo + o
|
725
|
+
combiner = proc{ |memo, o| memo + o }
|
660
726
|
expect(enum.in_threads.send(method, 0, &combiner)).
|
661
727
|
to eq(enum.send(method, 0, &combiner))
|
662
728
|
end
|
663
|
-
|
664
|
-
it 'raises exception in outer thread' do
|
665
|
-
check_test_exception(enum) do |threaded|
|
666
|
-
threaded.send(method){ fail TestException }
|
667
|
-
end
|
668
|
-
end
|
669
729
|
end
|
670
730
|
end
|
671
731
|
|
672
732
|
%w[max min minmax sort].each do |method|
|
673
|
-
|
733
|
+
describe_enum_method method do
|
674
734
|
it 'returns same result' do
|
675
|
-
comparer = proc{ |a, b| a
|
735
|
+
comparer = proc{ |a, b| a <=> b }
|
676
736
|
expect(enum.in_threads.send(method, &comparer)).
|
677
737
|
to eq(enum.send(method, &comparer))
|
678
738
|
end
|
679
|
-
|
680
|
-
it 'raises exception in outer thread' do
|
681
|
-
check_test_exception(enum) do |threaded|
|
682
|
-
threaded.send(method){ fail TestException }
|
683
|
-
end
|
684
|
-
end
|
685
739
|
end
|
686
740
|
end
|
687
741
|
|
688
742
|
%w[to_a entries].each do |method|
|
689
|
-
|
743
|
+
describe_enum_method method do
|
690
744
|
it 'returns same result' do
|
691
745
|
expect(enum.in_threads.send(method)).to eq(enum.send(method))
|
692
746
|
end
|
@@ -694,7 +748,7 @@ describe 'in_threads' do
|
|
694
748
|
end
|
695
749
|
|
696
750
|
%w[drop take].each do |method|
|
697
|
-
|
751
|
+
describe_enum_method method do
|
698
752
|
it 'returns same result' do
|
699
753
|
expect(enum.in_threads.send(method, 2)).to eq(enum.send(method, 2))
|
700
754
|
end
|
@@ -702,7 +756,7 @@ describe 'in_threads' do
|
|
702
756
|
end
|
703
757
|
|
704
758
|
%w[first].each do |method|
|
705
|
-
|
759
|
+
describe_enum_method method do
|
706
760
|
it 'returns same result' do
|
707
761
|
expect(enum.in_threads.send(method)).to eq(enum.send(method))
|
708
762
|
expect(enum.in_threads.send(method, 3)).to eq(enum.send(method, 3))
|
@@ -711,40 +765,25 @@ describe 'in_threads' do
|
|
711
765
|
end
|
712
766
|
|
713
767
|
%w[include? member?].each do |method|
|
714
|
-
|
768
|
+
describe_enum_method method do
|
715
769
|
it 'returns same result' do
|
716
|
-
expect(enum.in_threads.send(method,
|
717
|
-
to eq(enum.send(method, items[10]))
|
770
|
+
expect(enum.in_threads.send(method, enum.to_a[10])).to eq(enum.send(method, enum.to_a[10]))
|
718
771
|
end
|
719
772
|
end
|
720
773
|
end
|
721
774
|
|
722
|
-
describe_enum_method
|
723
|
-
let(:
|
775
|
+
describe_enum_method :each_with_object do
|
776
|
+
let(:block){ proc{ |o, h| h[o] = true } }
|
724
777
|
|
725
778
|
it 'returns same result' do
|
726
|
-
expect(enum.in_threads.each_with_object({}, &
|
727
|
-
to eq(enum.each_with_object({}, &runner))
|
728
|
-
end
|
729
|
-
|
730
|
-
it 'raises exception in outer thread' do
|
731
|
-
check_test_exception(enum) do |threaded|
|
732
|
-
threaded.each_with_object({}){ fail TestException }
|
733
|
-
end
|
779
|
+
expect(enum.in_threads.each_with_object({}, &block)).to eq(enum.each_with_object({}, &block))
|
734
780
|
end
|
735
781
|
end
|
736
782
|
|
737
783
|
%w[chunk slice_before slice_after].each do |method|
|
738
784
|
describe_enum_method method do
|
739
785
|
it 'returns same result' do
|
740
|
-
expect(enum.in_threads.send(method, &:
|
741
|
-
to eq(enum.send(method, &:check?).to_a)
|
742
|
-
end
|
743
|
-
|
744
|
-
it 'raises exception in outer thread' do
|
745
|
-
check_test_exception(enum) do |threaded|
|
746
|
-
threaded.send(method){ fail TestException }.to_a
|
747
|
-
end
|
786
|
+
expect(enum.in_threads.send(method, &:odd?).to_a).to eq(enum.send(method, &:odd?).to_a)
|
748
787
|
end
|
749
788
|
end
|
750
789
|
end
|