cool.io 1.9.5 → 1.10.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/.github/workflows/test.yaml +1 -0
- data/.github/workflows/valgrind.yaml +0 -3
- data/Rakefile +1 -0
- data/ext/cool.io/buffer.c +32 -18
- data/ext/cool.io/ev_wrap.h +1 -0
- data/ext/cool.io/iowatcher.c +18 -1
- data/ext/cool.io/loop.c +6 -4
- data/ext/cool.io/stat_watcher.c +30 -18
- data/ext/cool.io/timer_watcher.c +1 -1
- data/ext/cool.io/watcher.h +0 -17
- data/ext/libev/ev.c +73 -11
- data/ext/libev/ev_vars.h +4 -0
- data/ext/libev/ev_win32_stat.c +851 -0
- data/ext/libev/ev_wrap.h +4 -0
- data/lib/cool.io/dns_resolver.rb +1 -0
- data/lib/cool.io/loop.rb +5 -1
- data/lib/cool.io/version.rb +1 -1
- data/spec/detach_race_condition_spec.rb +23 -11
- data/spec/dns_spec.rb +11 -0
- data/spec/fixtures/windows_stat_native.c +1305 -0
- data/spec/iobuffer_spec.rb +57 -1
- data/spec/loop_spec.rb +31 -0
- data/spec/stat_watcher_spec.rb +55 -0
- data/spec/tcp_server_spec.rb +6 -6
- data/spec/watcher_reattach_spec.rb +54 -0
- data/spec/windows_stat_native_spec.rb +193 -0
- data/spec/windows_stat_watcher_spec.rb +363 -0
- metadata +13 -2
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
require File.expand_path('../spec_helper', __FILE__)
|
|
2
|
+
require 'tmpdir'
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
require 'rbconfig'
|
|
5
|
+
|
|
6
|
+
describe 'Windows notification-backed StatWatcher', :if => RUBY_PLATFORM =~ /mingw|mswin/ do
|
|
7
|
+
before do
|
|
8
|
+
@directory = Dir.mktmpdir('coolio-notify-')
|
|
9
|
+
@loop = Coolio::Loop.new
|
|
10
|
+
@watchers = []
|
|
11
|
+
@threads = []
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
after do
|
|
15
|
+
@threads.each(&:join)
|
|
16
|
+
@watchers.each { |watcher| watcher.detach if watcher.attached? }
|
|
17
|
+
FileUtils.remove_entry(@directory)
|
|
18
|
+
@loop = nil
|
|
19
|
+
@watchers = nil
|
|
20
|
+
GC.start # exercise IOCP cancellation and loop destruction
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def watch(path, interval = 60)
|
|
24
|
+
watcher = Coolio::StatWatcher.new(path, interval)
|
|
25
|
+
watcher.attach(@loop)
|
|
26
|
+
@watchers << watcher
|
|
27
|
+
watcher
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def root_file
|
|
31
|
+
path = File.join(File.expand_path('/'), "coolio-#{File.basename(@directory)}")
|
|
32
|
+
begin
|
|
33
|
+
File.write(path, 'a')
|
|
34
|
+
rescue Errno::EACCES, Errno::EPERM, Errno::EROFS
|
|
35
|
+
skip 'requires write access to the drive root'
|
|
36
|
+
end
|
|
37
|
+
path
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# With a 60-second stat interval, a callback within this deadline must come
|
|
41
|
+
# from native notifications. Mutate from another thread while select waits.
|
|
42
|
+
def await_change(watcher, size: nil, exists: nil, &mutation)
|
|
43
|
+
change = nil
|
|
44
|
+
first_previous = nil
|
|
45
|
+
reactor = @loop
|
|
46
|
+
callback_thread = nil
|
|
47
|
+
stopped = false
|
|
48
|
+
stop = -> do
|
|
49
|
+
unless stopped
|
|
50
|
+
stopped = true
|
|
51
|
+
reactor.stop
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
watcher.define_singleton_method(:on_change) do |previous, current|
|
|
55
|
+
first_previous ||= previous
|
|
56
|
+
if (size.nil? || current.size == size) && (exists.nil? || (current.nlink != 0) == exists)
|
|
57
|
+
change = [first_previous, current]
|
|
58
|
+
callback_thread = Thread.current
|
|
59
|
+
stop.call
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
deadline = Coolio::TimerWatcher.new(2, false)
|
|
63
|
+
deadline.on_timer { stop.call }
|
|
64
|
+
deadline.attach(@loop)
|
|
65
|
+
@threads << Thread.new { sleep 0.1; mutation.call }
|
|
66
|
+
@loop.run
|
|
67
|
+
expect(change).not_to be_nil
|
|
68
|
+
expect(callback_thread).to eq(Thread.current)
|
|
69
|
+
change
|
|
70
|
+
ensure
|
|
71
|
+
deadline.detach if deadline && deadline.attached?
|
|
72
|
+
@threads.each(&:join)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it 'wakes a blocked loop for a file size change without waiting for polling' do
|
|
76
|
+
path = File.join(@directory, 'file')
|
|
77
|
+
File.write(path, 'a')
|
|
78
|
+
previous, current = await_change(watch(path), size: 6) { File.write(path, 'longer') }
|
|
79
|
+
expect(previous.size).to eq(1)
|
|
80
|
+
expect(current.size).to eq(6)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it 'notifies for creation, deletion, and recreation of a watched file' do
|
|
84
|
+
path = File.join(@directory, 'file')
|
|
85
|
+
watcher = watch(path)
|
|
86
|
+
previous, current = await_change(watcher, size: 7, exists: true) { File.write(path, 'created') }
|
|
87
|
+
expect(previous.nlink).to eq(0)
|
|
88
|
+
expect(current.nlink).not_to eq(0)
|
|
89
|
+
_, current = await_change(watcher, exists: false) { File.delete(path) }
|
|
90
|
+
expect(current.nlink).to eq(0)
|
|
91
|
+
_, current = await_change(watcher, size: 9, exists: true) { File.write(path, 'recreated') }
|
|
92
|
+
expect(current.size).to eq(9)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
it 'notifies when a watched file is renamed away' do
|
|
96
|
+
path = File.join(@directory, 'file')
|
|
97
|
+
File.write(path, 'a')
|
|
98
|
+
_, current = await_change(watch(path)) { File.rename(path, "#{path}.old") }
|
|
99
|
+
expect(current.nlink).to eq(0)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
it 'supports Unicode parent directories and filenames' do
|
|
103
|
+
directory = File.join(@directory, "\u76e3\u8996")
|
|
104
|
+
Dir.mkdir(directory)
|
|
105
|
+
path = File.join(directory, "\u5909\u66f4.txt")
|
|
106
|
+
File.write(path, 'a')
|
|
107
|
+
_, current = await_change(watch(path), size: 6) { File.write(path, 'longer') }
|
|
108
|
+
expect(current.size).to eq(6)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
it 'matches notification names using Windows Unicode case folding' do
|
|
112
|
+
path = File.join(@directory, "\u00c4.txt")
|
|
113
|
+
File.write(path, 'a')
|
|
114
|
+
folded = File.join(@directory, "\u00e4.txt")
|
|
115
|
+
skip 'requires a case-insensitive directory' unless File.exist?(folded)
|
|
116
|
+
watcher = watch(folded)
|
|
117
|
+
_, current = await_change(watcher, size: 6) { File.write(path, 'longer') }
|
|
118
|
+
expect(current.size).to eq(6)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
it 'notifies for an extended-length file path below a directory' do
|
|
122
|
+
path = File.join(@directory, 'file')
|
|
123
|
+
File.write(path, 'a')
|
|
124
|
+
extended = "\\\\?\\#{path.tr('/', '\\')}"
|
|
125
|
+
_, current = await_change(watch(extended), size: 6) { File.write(path, 'longer') }
|
|
126
|
+
expect(current.size).to eq(6)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
it 'notifies for an extended-length file path at the drive root' do
|
|
130
|
+
path = root_file
|
|
131
|
+
extended = "\\\\?\\#{path.tr('/', '\\')}"
|
|
132
|
+
_, current = await_change(watch(extended), size: 6) { File.write(path, 'longer') }
|
|
133
|
+
expect(current.size).to eq(6)
|
|
134
|
+
ensure
|
|
135
|
+
File.delete(path) if path && File.exist?(path)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
it 'notifies for an extended-length drive root directory' do
|
|
139
|
+
path = root_file
|
|
140
|
+
child = "#{path}.child"
|
|
141
|
+
root = "\\\\?\\#{File.expand_path('/').tr('/', '\\')}"
|
|
142
|
+
watcher = watch(root)
|
|
143
|
+
sleep 1.1 # Directory timestamps are exposed as whole seconds.
|
|
144
|
+
await_change(watcher) { File.write(child, 'a') }
|
|
145
|
+
ensure
|
|
146
|
+
[path, child].compact.each { |file| File.delete(file) if File.exist?(file) }
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
it 'continues monitoring a shared directory after another watcher detaches' do
|
|
150
|
+
first = File.join(@directory, 'first')
|
|
151
|
+
second = File.join(@directory, 'second')
|
|
152
|
+
File.write(first, 'a')
|
|
153
|
+
File.write(second, 'a')
|
|
154
|
+
first_watcher = watch(first)
|
|
155
|
+
second_watcher = watch(second)
|
|
156
|
+
first_watcher.detach
|
|
157
|
+
_, current = await_change(second_watcher, size: 6) { File.write(second, 'longer') }
|
|
158
|
+
expect(current.size).to eq(6)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
it 'transcodes a Windows-31J path for the wide Windows APIs' do
|
|
162
|
+
path = File.join(@directory, "\u5909\u66f4.txt")
|
|
163
|
+
File.write(path, 'a')
|
|
164
|
+
watcher = watch(path.encode('Windows-31J'))
|
|
165
|
+
_, current = await_change(watcher, size: 6) { File.write(path, 'longer') }
|
|
166
|
+
expect(current.size).to eq(6)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
it 'transfers notification monitoring to another loop' do
|
|
170
|
+
path = File.join(@directory, 'file')
|
|
171
|
+
File.write(path, 'a')
|
|
172
|
+
watcher = watch(path)
|
|
173
|
+
old_loop = @loop
|
|
174
|
+
@loop = Coolio::Loop.new
|
|
175
|
+
watcher.attach(@loop)
|
|
176
|
+
old_loop.run_nonblock
|
|
177
|
+
_, current = await_change(watcher, size: 6) { File.write(path, 'longer') }
|
|
178
|
+
expect(current.size).to eq(6)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
it 'does not dispatch a queued change after disable and supports re-enable' do
|
|
182
|
+
path = File.join(@directory, 'file')
|
|
183
|
+
File.write(path, 'a')
|
|
184
|
+
watcher = watch(path)
|
|
185
|
+
calls = 0
|
|
186
|
+
watcher.define_singleton_method(:on_change) { |_, _| calls += 1 }
|
|
187
|
+
File.write(path, 'queued')
|
|
188
|
+
sleep 0.05
|
|
189
|
+
watcher.disable
|
|
190
|
+
@loop.run_nonblock
|
|
191
|
+
expect(calls).to eq(0)
|
|
192
|
+
watcher.enable
|
|
193
|
+
_, current = await_change(watcher, size: 12) { File.write(path, 'after enable') }
|
|
194
|
+
expect(current.size).to eq(12)
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
it 'falls back to polling when the parent is missing, then registers notifications' do
|
|
198
|
+
directory = File.join(@directory, 'missing')
|
|
199
|
+
path = File.join(directory, 'file')
|
|
200
|
+
watcher = watch(path, 0.1)
|
|
201
|
+
_, current = await_change(watcher, size: 7, exists: true) { Dir.mkdir(directory); File.write(path, 'created') }
|
|
202
|
+
expect(current.size).to eq(7)
|
|
203
|
+
# Reattach with a long interval to prove the now-existing parent is watched.
|
|
204
|
+
watcher.detach
|
|
205
|
+
_, current = await_change(watch(path), size: 18) { File.write(path, 'longer than before') }
|
|
206
|
+
expect(current.size).to eq(18)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
it 'handles directory entry changes' do
|
|
210
|
+
directory = File.join(@directory, 'directory')
|
|
211
|
+
Dir.mkdir(directory)
|
|
212
|
+
watcher = watch(directory)
|
|
213
|
+
# StatInfo exposes whole seconds for timestamps; cross a second boundary
|
|
214
|
+
# so this directory-only change is observable through the existing API.
|
|
215
|
+
sleep 1.1
|
|
216
|
+
await_change(watcher) { File.write(File.join(directory, 'child'), 'a') }
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
it 'recovers after a parent directory is moved and replaced' do
|
|
220
|
+
directory = File.join(@directory, 'parent')
|
|
221
|
+
Dir.mkdir(directory)
|
|
222
|
+
path = File.join(directory, 'file')
|
|
223
|
+
File.write(path, 'a')
|
|
224
|
+
watcher = watch(path, 0.1)
|
|
225
|
+
_, current = await_change(watcher, size: 11, exists: true) do
|
|
226
|
+
File.rename(directory, "#{directory}.old")
|
|
227
|
+
Dir.mkdir(directory)
|
|
228
|
+
File.write(path, 'replacement')
|
|
229
|
+
end
|
|
230
|
+
expect(current.size).to eq(11)
|
|
231
|
+
_, current = await_change(watcher, size: 5) { File.write(path, 'final') }
|
|
232
|
+
expect(current.size).to eq(5)
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
it 'keeps monitoring after an existing file is replaced' do
|
|
236
|
+
path = File.join(@directory, 'long-watched-filename.log')
|
|
237
|
+
replacement = File.join(@directory, 'replacement.log')
|
|
238
|
+
File.write(path, 'a')
|
|
239
|
+
File.write(replacement, 'replacement')
|
|
240
|
+
watcher = watch(path)
|
|
241
|
+
await_change(watcher, size: 11, exists: true) do
|
|
242
|
+
File.rename(path, "#{path}.old")
|
|
243
|
+
File.rename(replacement, path)
|
|
244
|
+
end
|
|
245
|
+
await_change(watcher, size: 12) { File.write(path, 'x', mode: 'a') }
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
it 'coalesces a burst of notifications and observes the final state' do
|
|
249
|
+
path = File.join(@directory, 'file')
|
|
250
|
+
File.write(path, 'a')
|
|
251
|
+
watcher = watch(path)
|
|
252
|
+
200.times do |index|
|
|
253
|
+
File.write(path, 'x' * (index + 2))
|
|
254
|
+
end
|
|
255
|
+
_, current = await_change(watcher, size: 5) { File.write(path, 'final') }
|
|
256
|
+
expect(current.size).to eq(5)
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
it 'validates a shared directory handle before attaching a new watcher' do
|
|
260
|
+
directory = File.join(@directory, 'parent')
|
|
261
|
+
Dir.mkdir(directory)
|
|
262
|
+
first = File.join(directory, 'first')
|
|
263
|
+
File.write(first, 'a')
|
|
264
|
+
watch(first)
|
|
265
|
+
|
|
266
|
+
File.rename(directory, "#{directory}.old")
|
|
267
|
+
Dir.mkdir(directory)
|
|
268
|
+
second = File.join(directory, 'second')
|
|
269
|
+
File.write(second, 'a')
|
|
270
|
+
_, current = await_change(watch(second), size: 5) { File.write(second, 'final') }
|
|
271
|
+
expect(current.size).to eq(5)
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
it 'keeps timers and Ruby threads responsive during sustained notifications' do
|
|
275
|
+
observed_size = nil
|
|
276
|
+
paths = 1000.times.map do |index|
|
|
277
|
+
path = File.join(@directory, "#{index}.log")
|
|
278
|
+
File.write(path, 'a')
|
|
279
|
+
watch(path).define_singleton_method(:on_change) do |_, current|
|
|
280
|
+
observed_size = current.size if index.zero?
|
|
281
|
+
end
|
|
282
|
+
path
|
|
283
|
+
end
|
|
284
|
+
clock = -> { Process.clock_gettime(Process::CLOCK_MONOTONIC) }
|
|
285
|
+
timer_gaps = []
|
|
286
|
+
thread_gaps = []
|
|
287
|
+
child = status = final_size = previous = deadline_at = nil
|
|
288
|
+
timer = Coolio::TimerWatcher.new(0.1, true)
|
|
289
|
+
timer.on_timer do
|
|
290
|
+
now = clock.call
|
|
291
|
+
timer_gaps << now - previous
|
|
292
|
+
previous = now
|
|
293
|
+
if child && (result = Process.wait2(child, Process::WNOHANG))
|
|
294
|
+
_, status = result
|
|
295
|
+
child = nil
|
|
296
|
+
final_size = File.size(paths.first)
|
|
297
|
+
end
|
|
298
|
+
# Process startup and notification delivery can outlast a fixed window
|
|
299
|
+
# on CI. Keep dispatching until the writer exits and its final size arrives.
|
|
300
|
+
@loop.stop if (status && observed_size == final_size) || now >= deadline_at
|
|
301
|
+
end
|
|
302
|
+
timer.attach(@loop)
|
|
303
|
+
@watchers << timer
|
|
304
|
+
done = false
|
|
305
|
+
# Use a separate process: a Ruby writer thread would itself be blocked by
|
|
306
|
+
# the GVL while the notification callback is draining the dirty set.
|
|
307
|
+
writer = <<~'RUBY'
|
|
308
|
+
path = ARGV.fetch(0)
|
|
309
|
+
sleep 0.3
|
|
310
|
+
clock = -> { Process.clock_gettime(Process::CLOCK_MONOTONIC) }
|
|
311
|
+
start = clock.call
|
|
312
|
+
writes = 0
|
|
313
|
+
while clock.call - start < 3
|
|
314
|
+
File.write(path, 'x', mode: 'a')
|
|
315
|
+
writes += 1
|
|
316
|
+
sleep [start + writes.fdiv(64) - clock.call, 0].max
|
|
317
|
+
end
|
|
318
|
+
RUBY
|
|
319
|
+
child = Process.spawn(RbConfig.ruby, '-e', writer, paths.first)
|
|
320
|
+
@threads << Thread.new do
|
|
321
|
+
last = clock.call
|
|
322
|
+
until done
|
|
323
|
+
sleep 0.05
|
|
324
|
+
now = clock.call
|
|
325
|
+
thread_gaps << now - last
|
|
326
|
+
last = now
|
|
327
|
+
end
|
|
328
|
+
end
|
|
329
|
+
previous = clock.call
|
|
330
|
+
deadline_at = previous + 15
|
|
331
|
+
@loop.run
|
|
332
|
+
done = true
|
|
333
|
+
@threads.each(&:join)
|
|
334
|
+
expect(status).not_to be_nil
|
|
335
|
+
expect(status.success?).to eq(true)
|
|
336
|
+
expect(timer_gaps.max).to be < 2
|
|
337
|
+
expect(thread_gaps.max).to be < 2
|
|
338
|
+
expect(final_size).to be > 1
|
|
339
|
+
expect(observed_size).to eq(final_size)
|
|
340
|
+
ensure
|
|
341
|
+
done = true
|
|
342
|
+
if child
|
|
343
|
+
begin
|
|
344
|
+
Process.kill('KILL', child)
|
|
345
|
+
rescue Errno::ESRCH
|
|
346
|
+
end
|
|
347
|
+
Process.wait(child)
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
it 'can repeatedly detach and reattach while changes are pending' do
|
|
352
|
+
path = File.join(@directory, 'file')
|
|
353
|
+
File.write(path, 'a')
|
|
354
|
+
watcher = watch(path)
|
|
355
|
+
50.times do |index|
|
|
356
|
+
File.write(path, 'x' * (index + 2))
|
|
357
|
+
watcher.detach
|
|
358
|
+
watcher.attach(@loop)
|
|
359
|
+
end
|
|
360
|
+
_, current = await_change(watcher, size: 5) { File.write(path, 'final') }
|
|
361
|
+
expect(current.size).to eq(5)
|
|
362
|
+
end
|
|
363
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: cool.io
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.10.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tony Arcieri
|
|
@@ -119,6 +119,7 @@ files:
|
|
|
119
119
|
- ext/libev/ev_select.c
|
|
120
120
|
- ext/libev/ev_vars.h
|
|
121
121
|
- ext/libev/ev_win32.c
|
|
122
|
+
- ext/libev/ev_win32_stat.c
|
|
122
123
|
- ext/libev/ev_wrap.h
|
|
123
124
|
- ext/libev/test_libev_win32.c
|
|
124
125
|
- gems.rb
|
|
@@ -143,7 +144,9 @@ files:
|
|
|
143
144
|
- spec/async_watcher_spec.rb
|
|
144
145
|
- spec/detach_race_condition_spec.rb
|
|
145
146
|
- spec/dns_spec.rb
|
|
147
|
+
- spec/fixtures/windows_stat_native.c
|
|
146
148
|
- spec/iobuffer_spec.rb
|
|
149
|
+
- spec/loop_spec.rb
|
|
147
150
|
- spec/spec_helper.rb
|
|
148
151
|
- spec/stat_watcher_spec.rb
|
|
149
152
|
- spec/tcp_server_spec.rb
|
|
@@ -152,6 +155,9 @@ files:
|
|
|
152
155
|
- spec/udp_socket_spec.rb
|
|
153
156
|
- spec/unix_listener_spec.rb
|
|
154
157
|
- spec/unix_server_spec.rb
|
|
158
|
+
- spec/watcher_reattach_spec.rb
|
|
159
|
+
- spec/windows_stat_native_spec.rb
|
|
160
|
+
- spec/windows_stat_watcher_spec.rb
|
|
155
161
|
homepage: https://github.com/socketry/cool.io
|
|
156
162
|
licenses:
|
|
157
163
|
- MIT
|
|
@@ -170,14 +176,16 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
170
176
|
- !ruby/object:Gem::Version
|
|
171
177
|
version: '0'
|
|
172
178
|
requirements: []
|
|
173
|
-
rubygems_version: 4.0.
|
|
179
|
+
rubygems_version: 4.0.20
|
|
174
180
|
specification_version: 4
|
|
175
181
|
summary: A cool framework for doing high performance I/O in Ruby
|
|
176
182
|
test_files:
|
|
177
183
|
- spec/async_watcher_spec.rb
|
|
178
184
|
- spec/detach_race_condition_spec.rb
|
|
179
185
|
- spec/dns_spec.rb
|
|
186
|
+
- spec/fixtures/windows_stat_native.c
|
|
180
187
|
- spec/iobuffer_spec.rb
|
|
188
|
+
- spec/loop_spec.rb
|
|
181
189
|
- spec/spec_helper.rb
|
|
182
190
|
- spec/stat_watcher_spec.rb
|
|
183
191
|
- spec/tcp_server_spec.rb
|
|
@@ -186,3 +194,6 @@ test_files:
|
|
|
186
194
|
- spec/udp_socket_spec.rb
|
|
187
195
|
- spec/unix_listener_spec.rb
|
|
188
196
|
- spec/unix_server_spec.rb
|
|
197
|
+
- spec/watcher_reattach_spec.rb
|
|
198
|
+
- spec/windows_stat_native_spec.rb
|
|
199
|
+
- spec/windows_stat_watcher_spec.rb
|