mt-libuv 4.1.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 +7 -0
- data/.gitignore +22 -0
- data/.gitmodules +6 -0
- data/.rspec +1 -0
- data/.travis.yml +24 -0
- data/Gemfile +9 -0
- data/LICENSE +24 -0
- data/README.md +195 -0
- data/Rakefile +31 -0
- data/ext/README.md +6 -0
- data/ext/Rakefile +28 -0
- data/lib/mt-libuv/async.rb +51 -0
- data/lib/mt-libuv/check.rb +59 -0
- data/lib/mt-libuv/coroutines.rb +79 -0
- data/lib/mt-libuv/dns.rb +98 -0
- data/lib/mt-libuv/error.rb +88 -0
- data/lib/mt-libuv/ext/ext.rb +322 -0
- data/lib/mt-libuv/ext/platform/darwin_x64.rb +61 -0
- data/lib/mt-libuv/ext/platform/unix.rb +69 -0
- data/lib/mt-libuv/ext/platform/windows.rb +83 -0
- data/lib/mt-libuv/ext/tasks/mac.rb +24 -0
- data/lib/mt-libuv/ext/tasks/unix.rb +42 -0
- data/lib/mt-libuv/ext/tasks/win.rb +29 -0
- data/lib/mt-libuv/ext/tasks.rb +27 -0
- data/lib/mt-libuv/ext/types.rb +253 -0
- data/lib/mt-libuv/fiber_pool.rb +83 -0
- data/lib/mt-libuv/file.rb +309 -0
- data/lib/mt-libuv/filesystem.rb +263 -0
- data/lib/mt-libuv/fs_event.rb +37 -0
- data/lib/mt-libuv/handle.rb +108 -0
- data/lib/mt-libuv/idle.rb +59 -0
- data/lib/mt-libuv/mixins/accessors.rb +41 -0
- data/lib/mt-libuv/mixins/assertions.rb +25 -0
- data/lib/mt-libuv/mixins/fs_checks.rb +96 -0
- data/lib/mt-libuv/mixins/listener.rb +69 -0
- data/lib/mt-libuv/mixins/net.rb +42 -0
- data/lib/mt-libuv/mixins/resource.rb +30 -0
- data/lib/mt-libuv/mixins/stream.rb +276 -0
- data/lib/mt-libuv/pipe.rb +217 -0
- data/lib/mt-libuv/prepare.rb +59 -0
- data/lib/mt-libuv/q.rb +475 -0
- data/lib/mt-libuv/reactor.rb +567 -0
- data/lib/mt-libuv/signal.rb +62 -0
- data/lib/mt-libuv/spawn.rb +113 -0
- data/lib/mt-libuv/tcp.rb +465 -0
- data/lib/mt-libuv/timer.rb +107 -0
- data/lib/mt-libuv/tty.rb +42 -0
- data/lib/mt-libuv/udp.rb +302 -0
- data/lib/mt-libuv/version.rb +5 -0
- data/lib/mt-libuv/work.rb +86 -0
- data/lib/mt-libuv.rb +80 -0
- data/mt-libuv.gemspec +62 -0
- data/spec/async_spec.rb +67 -0
- data/spec/coroutines_spec.rb +121 -0
- data/spec/cpu_spec.rb +10 -0
- data/spec/defer_spec.rb +906 -0
- data/spec/dns_spec.rb +110 -0
- data/spec/dsl_spec.rb +43 -0
- data/spec/filesystem_spec.rb +270 -0
- data/spec/idle_spec.rb +44 -0
- data/spec/pipe_spec.rb +151 -0
- data/spec/spawn_spec.rb +119 -0
- data/spec/tcp_spec.rb +272 -0
- data/spec/test.sh +4 -0
- data/spec/test_fail.sh +3 -0
- data/spec/test_read.sh +3 -0
- data/spec/timer_spec.rb +14 -0
- data/spec/udp_spec.rb +73 -0
- data/spec/zen_spec.rb +34 -0
- metadata +196 -0
data/spec/spawn_spec.rb
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'mt-libuv'
|
2
|
+
|
3
|
+
describe MTLibuv::Spawn do
|
4
|
+
describe 'basic spawn of external process' do
|
5
|
+
it "should accept arguments and return an termination signal code" do
|
6
|
+
@reactor = MTLibuv::Reactor.new
|
7
|
+
@log = []
|
8
|
+
@reactor.run { |reactor|
|
9
|
+
begin
|
10
|
+
p = MTLibuv::Spawn.new(reactor, './spec/test.sh', args: ['arg1', 'arg2'], env: ['SOME_VAR=123'])
|
11
|
+
p.stdout.progress do |data|
|
12
|
+
@log << data
|
13
|
+
end
|
14
|
+
p.stdout.start_read
|
15
|
+
@log << p.value
|
16
|
+
rescue => e
|
17
|
+
@log << e
|
18
|
+
@reactor.stop
|
19
|
+
end
|
20
|
+
}
|
21
|
+
|
22
|
+
term_signal = @log.pop
|
23
|
+
expect(term_signal).to be(0)
|
24
|
+
|
25
|
+
expect(@log[0]).to eq("123\narg1\narg2\n")
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should return termination signal if exit code was 0" do
|
29
|
+
@reactor = MTLibuv::Reactor.new
|
30
|
+
@log = []
|
31
|
+
@reactor.run { |reactor|
|
32
|
+
begin
|
33
|
+
p = MTLibuv::Spawn.new(reactor, './spec/test.sh', args: ['arg1', 'arg2'], env: ['SOME_VAR=123'])
|
34
|
+
p.kill
|
35
|
+
p.stdout.progress do |data|
|
36
|
+
@log << data
|
37
|
+
end
|
38
|
+
p.stdout.start_read
|
39
|
+
@log << p.value
|
40
|
+
rescue => e
|
41
|
+
@log << e
|
42
|
+
@reactor.stop
|
43
|
+
end
|
44
|
+
}
|
45
|
+
|
46
|
+
term_signal = @log.pop
|
47
|
+
expect(term_signal).to be(2)
|
48
|
+
expect(@log[0]).to be(nil)
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should fail if exit code was not 0 and read output from stderr" do
|
52
|
+
@reactor = MTLibuv::Reactor.new
|
53
|
+
@log = []
|
54
|
+
@reactor.run { |reactor|
|
55
|
+
begin
|
56
|
+
p = MTLibuv::Spawn.new(reactor, './spec/test_fail.sh')
|
57
|
+
p.stderr.progress do |data|
|
58
|
+
@log << data
|
59
|
+
end
|
60
|
+
p.stderr.start_read
|
61
|
+
@log << p.value
|
62
|
+
rescue => e
|
63
|
+
@log << e
|
64
|
+
@reactor.stop
|
65
|
+
end
|
66
|
+
}
|
67
|
+
|
68
|
+
stderr = @log[0]
|
69
|
+
e = @log[1]
|
70
|
+
expect(e.class).to be(::MTLibuv::Error::ProcessExitCode)
|
71
|
+
expect(e.exit_status).to be(1)
|
72
|
+
expect(e.term_signal).to be(0)
|
73
|
+
expect(stderr.length).to be > 0
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should be interactive" do
|
77
|
+
@reactor = MTLibuv::Reactor.new
|
78
|
+
@log = []
|
79
|
+
@reactor.run { |reactor|
|
80
|
+
begin
|
81
|
+
p = MTLibuv::Spawn.new(reactor, './spec/test_read.sh')
|
82
|
+
p.stdout.progress do |data|
|
83
|
+
@log << data
|
84
|
+
end
|
85
|
+
p.stdout.start_read
|
86
|
+
p.stdin.write("2017\n")
|
87
|
+
@log << p.value
|
88
|
+
rescue => e
|
89
|
+
@log << e
|
90
|
+
@reactor.stop
|
91
|
+
end
|
92
|
+
}
|
93
|
+
|
94
|
+
term_signal = @log.pop
|
95
|
+
expect(term_signal).to be(0)
|
96
|
+
|
97
|
+
expect(@log[0]).to eq("you entered 2017 - sweet\n")
|
98
|
+
end
|
99
|
+
|
100
|
+
it "should support inheriting parent processes streams" do
|
101
|
+
@reactor = MTLibuv::Reactor.new
|
102
|
+
@log = []
|
103
|
+
@reactor.run { |reactor|
|
104
|
+
begin
|
105
|
+
p = MTLibuv::Spawn.new(reactor, './spec/test.sh', args: ['arg1', 'arg2'], env: ['SOME_VAR=123'], mode: :inherit)
|
106
|
+
@log << p.stdout
|
107
|
+
@log << p.value
|
108
|
+
rescue => e
|
109
|
+
@log << e
|
110
|
+
@reactor.stop
|
111
|
+
end
|
112
|
+
}
|
113
|
+
|
114
|
+
term_signal = @log.pop
|
115
|
+
expect(term_signal).to be(0)
|
116
|
+
expect(@log[0]).to be(nil)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
data/spec/tcp_spec.rb
ADDED
@@ -0,0 +1,272 @@
|
|
1
|
+
require 'mt-libuv'
|
2
|
+
require 'thread'
|
3
|
+
|
4
|
+
|
5
|
+
describe MTLibuv::TCP do
|
6
|
+
before :each do
|
7
|
+
@log = []
|
8
|
+
@general_failure = []
|
9
|
+
|
10
|
+
@reactor = MTLibuv::Reactor.new
|
11
|
+
@server = @reactor.tcp
|
12
|
+
@client = @reactor.tcp
|
13
|
+
@timeout = @reactor.timer do
|
14
|
+
@reactor.stop
|
15
|
+
@reactor2.stop if @reactor2
|
16
|
+
@general_failure << "test timed out"
|
17
|
+
end
|
18
|
+
@timeout.start(5000)
|
19
|
+
|
20
|
+
@reactor.all(@server, @client, @timeout).catch do |reason|
|
21
|
+
@general_failure << reason.inspect
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
@pipefile = "/tmp/test-pipe.pipe"
|
26
|
+
@reactor.notifier do |error, context|
|
27
|
+
begin
|
28
|
+
@general_failure << "Log called: #{context}\n#{error.message}\n#{error.backtrace.join("\n") if error.backtrace}\n"
|
29
|
+
rescue Exception => e
|
30
|
+
@general_failure << "error in logger #{e.inspect}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
begin
|
35
|
+
File.unlink(@pipefile)
|
36
|
+
rescue
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe 'basic client server' do
|
41
|
+
it "should send a ping and return a pong", :network => true do
|
42
|
+
@reactor.run { |reactor|
|
43
|
+
@server.bind('127.0.0.1', 34567) do |client|
|
44
|
+
client.progress do |data|
|
45
|
+
@log << data
|
46
|
+
|
47
|
+
client.write('pong')
|
48
|
+
end
|
49
|
+
client.start_read
|
50
|
+
end
|
51
|
+
|
52
|
+
# catch errors
|
53
|
+
@server.catch do |reason|
|
54
|
+
@general_failure << reason.inspect
|
55
|
+
end
|
56
|
+
|
57
|
+
# start listening
|
58
|
+
@server.listen(1024)
|
59
|
+
|
60
|
+
|
61
|
+
|
62
|
+
# connect client to server
|
63
|
+
@client.connect('127.0.0.1', 34567) do |client|
|
64
|
+
client.progress do |data|
|
65
|
+
@log << data
|
66
|
+
|
67
|
+
@client.shutdown
|
68
|
+
end
|
69
|
+
|
70
|
+
@client.start_read
|
71
|
+
@client.write('ping')
|
72
|
+
end
|
73
|
+
|
74
|
+
# catch errors
|
75
|
+
@client.catch do |reason|
|
76
|
+
@general_failure << reason.inspect
|
77
|
+
end
|
78
|
+
|
79
|
+
# close the handle
|
80
|
+
@client.finally do
|
81
|
+
@server.close
|
82
|
+
@reactor.stop
|
83
|
+
end
|
84
|
+
}
|
85
|
+
|
86
|
+
expect(@general_failure).to eq([])
|
87
|
+
expect(@log).to eq(['ping', 'pong'])
|
88
|
+
end
|
89
|
+
|
90
|
+
it "should work with coroutines", :network => true do
|
91
|
+
@reactor.run { |reactor|
|
92
|
+
@server.bind('127.0.0.1', 34567) do |client|
|
93
|
+
client.progress do |data|
|
94
|
+
@log << data
|
95
|
+
|
96
|
+
client.write('pong')
|
97
|
+
end
|
98
|
+
client.start_read
|
99
|
+
end
|
100
|
+
|
101
|
+
# catch errors
|
102
|
+
@server.catch do |reason|
|
103
|
+
@general_failure << reason.inspect
|
104
|
+
end
|
105
|
+
|
106
|
+
# start listening
|
107
|
+
@server.listen(1024)
|
108
|
+
|
109
|
+
# connect client to server
|
110
|
+
@client.progress do |data|
|
111
|
+
@log << data
|
112
|
+
|
113
|
+
addrinfo = @reactor.lookup('127.0.0.1')
|
114
|
+
@log << addrinfo[0][0]
|
115
|
+
|
116
|
+
@client.shutdown
|
117
|
+
end
|
118
|
+
# catch errors
|
119
|
+
@client.catch do |reason|
|
120
|
+
@general_failure << reason.inspect
|
121
|
+
end
|
122
|
+
|
123
|
+
# close the handle
|
124
|
+
@client.finally do
|
125
|
+
@server.close
|
126
|
+
@reactor.stop
|
127
|
+
end
|
128
|
+
@client.connect('127.0.0.1', 34567)
|
129
|
+
@client.start_read
|
130
|
+
@client.write('ping')
|
131
|
+
}
|
132
|
+
|
133
|
+
expect(@general_failure).to eq([])
|
134
|
+
expect(@log).to eq(['ping', 'pong', '127.0.0.1'])
|
135
|
+
end
|
136
|
+
|
137
|
+
it "should quack a bit like an IO object", :network => true do
|
138
|
+
@reactor.run { |reactor|
|
139
|
+
@server.bind('127.0.0.1', 34567) do |client|
|
140
|
+
@log << client.read(4)
|
141
|
+
client.write('pong')
|
142
|
+
end
|
143
|
+
|
144
|
+
# catch errors
|
145
|
+
@server.catch do |reason|
|
146
|
+
@general_failure << reason.inspect
|
147
|
+
end
|
148
|
+
|
149
|
+
# start listening
|
150
|
+
@server.listen(1024)
|
151
|
+
|
152
|
+
# catch errors
|
153
|
+
@client.catch do |reason|
|
154
|
+
@general_failure << reason.inspect
|
155
|
+
end
|
156
|
+
|
157
|
+
# close the handle
|
158
|
+
@client.finally do
|
159
|
+
@server.close
|
160
|
+
@reactor.stop
|
161
|
+
end
|
162
|
+
@client.connect('127.0.0.1', 34567)
|
163
|
+
@client.write('ping')
|
164
|
+
@log << @client.read(4)
|
165
|
+
addrinfo = @reactor.lookup('127.0.0.1')
|
166
|
+
@log << addrinfo[0][0]
|
167
|
+
@client.shutdown
|
168
|
+
}
|
169
|
+
|
170
|
+
expect(@general_failure).to eq([])
|
171
|
+
expect(@log).to eq(['ping', 'pong', '127.0.0.1'])
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
it "should handle requests on different threads", :network => true do
|
176
|
+
@sync = Mutex.new
|
177
|
+
|
178
|
+
@reactor.run { |reactor|
|
179
|
+
@remote = nil
|
180
|
+
@server.bind('127.0.0.1', 45678) do |client|
|
181
|
+
@remote.write2(client)
|
182
|
+
end
|
183
|
+
|
184
|
+
# catch errors
|
185
|
+
@server.catch do |reason|
|
186
|
+
@general_failure << reason.inspect
|
187
|
+
end
|
188
|
+
|
189
|
+
|
190
|
+
@pipeserve = @reactor.pipe(true)
|
191
|
+
@pipeserve.bind(@pipefile) do |client|
|
192
|
+
@remote = client
|
193
|
+
|
194
|
+
# start listening on TCP server
|
195
|
+
@server.listen(1024)
|
196
|
+
|
197
|
+
# connect client to server
|
198
|
+
@client.connect('127.0.0.1', 45678) do |client|
|
199
|
+
client.progress do |data|
|
200
|
+
@sync.synchronize {
|
201
|
+
@log << data
|
202
|
+
}
|
203
|
+
@client.shutdown
|
204
|
+
end
|
205
|
+
|
206
|
+
@client.start_read
|
207
|
+
@client.write('ping')
|
208
|
+
end
|
209
|
+
|
210
|
+
@pipeserve.getsockname
|
211
|
+
end
|
212
|
+
|
213
|
+
# start listening
|
214
|
+
@pipeserve.listen(1024)
|
215
|
+
|
216
|
+
|
217
|
+
|
218
|
+
# catch errors
|
219
|
+
@client.catch do |reason|
|
220
|
+
@general_failure << reason.inspect
|
221
|
+
end
|
222
|
+
|
223
|
+
# close the handle
|
224
|
+
@client.finally do
|
225
|
+
@server.close
|
226
|
+
@pipeserve.close
|
227
|
+
end
|
228
|
+
|
229
|
+
|
230
|
+
|
231
|
+
Thread.new do
|
232
|
+
@reactor2 = MTLibuv::Reactor.new
|
233
|
+
@reactor2.notifier do |error, context|
|
234
|
+
begin
|
235
|
+
@general_failure << "Log called: #{context}\n#{error.message}\n#{error.backtrace.join("\n") if error.backtrace}\n"
|
236
|
+
rescue Exception
|
237
|
+
@general_failure << "error in logger #{e.inspect}"
|
238
|
+
end
|
239
|
+
end
|
240
|
+
@pipeclient = @reactor2.pipe(true)
|
241
|
+
|
242
|
+
|
243
|
+
@reactor2.run do |reactor|
|
244
|
+
# connect client to server
|
245
|
+
@pipeclient.connect(@pipefile) do |client|
|
246
|
+
@pipeclient.progress do |data|
|
247
|
+
connection = @pipeclient.check_pending
|
248
|
+
|
249
|
+
connection.progress do |data|
|
250
|
+
@sync.synchronize {
|
251
|
+
@log << data
|
252
|
+
}
|
253
|
+
connection.write('pong')
|
254
|
+
end
|
255
|
+
connection.start_read
|
256
|
+
connection.finally do
|
257
|
+
@pipeclient.close
|
258
|
+
@reactor2.stop
|
259
|
+
@reactor.stop
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
@pipeclient.start_read
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
267
|
+
}
|
268
|
+
|
269
|
+
expect(@general_failure).to eq([])
|
270
|
+
expect(@log).to eq(['ping', 'pong'])
|
271
|
+
end
|
272
|
+
end
|
data/spec/test.sh
ADDED
data/spec/test_fail.sh
ADDED
data/spec/test_read.sh
ADDED
data/spec/timer_spec.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'mt-libuv'
|
2
|
+
|
3
|
+
describe MTLibuv::Timer do
|
4
|
+
describe 'setting properties' do
|
5
|
+
it "should allow repeat to be set" do
|
6
|
+
@reactor = MTLibuv::Reactor.new
|
7
|
+
@reactor.run { |logger|
|
8
|
+
@timer = @reactor.timer
|
9
|
+
@timer.repeat = 5
|
10
|
+
expect(@timer.repeat).to eq(5)
|
11
|
+
}
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/spec/udp_spec.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'mt-libuv'
|
2
|
+
require 'thread'
|
3
|
+
|
4
|
+
|
5
|
+
describe MTLibuv::UDP do
|
6
|
+
before :each do
|
7
|
+
@log = []
|
8
|
+
@general_failure = []
|
9
|
+
|
10
|
+
@reactor = MTLibuv::Reactor.new
|
11
|
+
@server = @reactor.udp
|
12
|
+
@client = @reactor.udp
|
13
|
+
@timeout = @reactor.timer do
|
14
|
+
@reactor.stop
|
15
|
+
@general_failure << "test timed out"
|
16
|
+
end
|
17
|
+
@timeout.start(5000)
|
18
|
+
|
19
|
+
@reactor.notifier do |error, context|
|
20
|
+
begin
|
21
|
+
@general_failure << "Log called: #{context}\n#{error.message}\n#{error.backtrace.join("\n") if error.backtrace}\n"
|
22
|
+
rescue Exception => e
|
23
|
+
@general_failure << "error in logger #{e.inspect}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
@reactor.all(@server, @client, @timeout).catch do |reason|
|
28
|
+
@general_failure << reason.inspect
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe 'basic client server' do
|
33
|
+
it "should send a ping and return a pong", :network => true do
|
34
|
+
@reactor.run { |reactor|
|
35
|
+
@server.bind('127.0.0.1', 34567)
|
36
|
+
@server.progress do |data, ip, port, server|
|
37
|
+
@log << data
|
38
|
+
server.send(ip, port, 'pong')
|
39
|
+
end
|
40
|
+
@server.start_read
|
41
|
+
|
42
|
+
# catch errors
|
43
|
+
@server.catch do |reason|
|
44
|
+
@general_failure << reason.inspect
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
# connect client to server
|
49
|
+
@client.bind('127.0.0.1', 34568)
|
50
|
+
@client.progress do |data, ip, port, client|
|
51
|
+
@log << data
|
52
|
+
client.close
|
53
|
+
end
|
54
|
+
@client.start_read
|
55
|
+
@client.send('127.0.0.1', 34567, 'ping')
|
56
|
+
|
57
|
+
# catch errors
|
58
|
+
@client.catch do |reason|
|
59
|
+
@general_failure << reason.inspect
|
60
|
+
end
|
61
|
+
|
62
|
+
# close the handle
|
63
|
+
@client.finally do
|
64
|
+
@server.close
|
65
|
+
@reactor.stop
|
66
|
+
end
|
67
|
+
}
|
68
|
+
|
69
|
+
expect(@log).to eq(['ping', 'pong'])
|
70
|
+
expect(@general_failure).to eq([])
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
data/spec/zen_spec.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'mt-libuv'
|
2
|
+
|
3
|
+
|
4
|
+
describe MTLibuv::Listener do
|
5
|
+
it "should ensure there are no remaining object references in callbacks", network: true do
|
6
|
+
require 'objspace'
|
7
|
+
|
8
|
+
checked = []
|
9
|
+
|
10
|
+
# These are created by loop objects and are never cleaned up
|
11
|
+
# This is OK as the loops are expected to execute for the life of the application
|
12
|
+
except = [::MTLibuv::Async, ::MTLibuv::Timer, ::MTLibuv::Prepare, ::MTLibuv::Signal]
|
13
|
+
|
14
|
+
ObjectSpace.each_object(Class) do |cls|
|
15
|
+
next unless cls.ancestors.include? ::MTLibuv::Handle
|
16
|
+
next if checked.include? cls
|
17
|
+
checked << cls
|
18
|
+
|
19
|
+
values = cls.callback_lookup.values
|
20
|
+
values.select! {|val| except.include?(val.class) ? false : val.class }
|
21
|
+
|
22
|
+
if values.length > 0
|
23
|
+
puts "\nMemory Leak in #{cls} with #{values.length} left over objects"
|
24
|
+
puts "Checked #{checked.length} classes, objects are:"
|
25
|
+
values.each do |val|
|
26
|
+
puts "\n#{val}\n"
|
27
|
+
end
|
28
|
+
raise 'Free the machines!'
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
expect(checked.length).to be > 3
|
33
|
+
end
|
34
|
+
end
|