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
data/spec/iobuffer_spec.rb
CHANGED
|
@@ -36,6 +36,7 @@ describe Cool.io::Buffer do
|
|
|
36
36
|
buffer << "foo"
|
|
37
37
|
expect { buffer.read 0 }.to raise_error ArgumentError
|
|
38
38
|
expect { buffer.read(-1) }.to raise_error ArgumentError
|
|
39
|
+
expect { buffer.read(-(2**100)) }.to raise_error ArgumentError
|
|
39
40
|
end
|
|
40
41
|
|
|
41
42
|
it "clamps a length which does not fit in a C int to the buffer size" do
|
|
@@ -43,8 +44,38 @@ describe Cool.io::Buffer do
|
|
|
43
44
|
expect(buffer.read 2**31).to eq "foobar"
|
|
44
45
|
expect(buffer.size).to eq 0
|
|
45
46
|
end
|
|
47
|
+
|
|
48
|
+
it "clamps lengths larger than an unsigned long or size_t to the buffer size" do
|
|
49
|
+
[2**32, 2**64, 2**100].each do |length|
|
|
50
|
+
buffer << "foobar"
|
|
51
|
+
expect(buffer.read(length)).to eq "foobar"
|
|
52
|
+
expect(buffer.size).to eq 0
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it "keeps integer coercion for numeric and to_int lengths" do
|
|
57
|
+
length = Object.new
|
|
58
|
+
def length.to_int
|
|
59
|
+
2
|
|
60
|
+
end
|
|
61
|
+
buffer << "foobar"
|
|
62
|
+
expect(buffer.read(length)).to eq "fo"
|
|
63
|
+
expect(buffer.read(2.9)).to eq "ob"
|
|
64
|
+
expect(buffer.to_str).to eq "ar"
|
|
65
|
+
end
|
|
46
66
|
end
|
|
47
|
-
|
|
67
|
+
|
|
68
|
+
it "raises RangeError when to_str is called on a buffer past LONG_MAX",
|
|
69
|
+
:if => [0].pack("l!").bytesize == 4 do
|
|
70
|
+
long_max = 2**31 - 1
|
|
71
|
+
node_size = 16 * 1024 * 1024
|
|
72
|
+
big = Cool.io::Buffer.new(node_size)
|
|
73
|
+
chunk = "x" * node_size
|
|
74
|
+
((long_max / node_size) + 1).times { big << chunk }
|
|
75
|
+
expect(big.size).to be > long_max
|
|
76
|
+
expect { big.to_str }.to raise_error(RangeError)
|
|
77
|
+
end
|
|
78
|
+
|
|
48
79
|
describe "provides methods for performing non-blocking I/O" do
|
|
49
80
|
require "tempfile"
|
|
50
81
|
|
|
@@ -138,6 +169,31 @@ describe Cool.io::Buffer do
|
|
|
138
169
|
expect(buffer.size).to eq 0
|
|
139
170
|
expect(buffer.empty?).to eq true
|
|
140
171
|
end
|
|
172
|
+
|
|
173
|
+
it "reuses pooled nodes instead of leaking them" do
|
|
174
|
+
skip "GC.stat(:malloc_increase_bytes) is not available" unless GC.stat.key?(:malloc_increase_bytes)
|
|
175
|
+
node_size = Cool.io::Buffer.default_node_size
|
|
176
|
+
data = "a" * (node_size * 2 + 1)
|
|
177
|
+
cycle = proc do
|
|
178
|
+
buffer << data
|
|
179
|
+
buffer.clear
|
|
180
|
+
buffer << "x"
|
|
181
|
+
buffer.clear
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Marking a Buffer frees its pool, so keep GC off from the warm-up on.
|
|
185
|
+
GC.start
|
|
186
|
+
gc_was_disabled = GC.disable
|
|
187
|
+
begin
|
|
188
|
+
3.times { cycle.call }
|
|
189
|
+
before = GC.stat(:malloc_increase_bytes)
|
|
190
|
+
200.times { cycle.call }
|
|
191
|
+
grown = GC.stat(:malloc_increase_bytes) - before
|
|
192
|
+
ensure
|
|
193
|
+
GC.enable unless gc_was_disabled
|
|
194
|
+
end
|
|
195
|
+
expect(grown).to be < node_size * 4
|
|
196
|
+
end
|
|
141
197
|
end
|
|
142
198
|
|
|
143
199
|
context "#read_frame" do
|
data/spec/loop_spec.rb
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
require File.expand_path('../spec_helper', __FILE__)
|
|
2
|
+
require 'rbconfig'
|
|
3
|
+
require 'open3'
|
|
4
|
+
|
|
5
|
+
describe Cool.io::Loop do
|
|
6
|
+
it "ignores $LIBEV_FLAGS when skip_environment is true" do
|
|
7
|
+
script = <<~'RUBY'
|
|
8
|
+
require 'cool.io'
|
|
9
|
+
loop = Cool.io::Loop.new(skip_environment: true)
|
|
10
|
+
fired = false
|
|
11
|
+
timer = Cool.io::TimerWatcher.new(0.01)
|
|
12
|
+
timer.on_timer { fired = true; timer.detach }
|
|
13
|
+
timer.attach(loop)
|
|
14
|
+
loop.run
|
|
15
|
+
print fired
|
|
16
|
+
RUBY
|
|
17
|
+
lib = File.expand_path('../lib', __dir__)
|
|
18
|
+
# No CI platform has the Solaris event port backend, so honoring this would leave no backend.
|
|
19
|
+
env = { 'LIBEV_FLAGS' => Cool.io::Loop::EVBACKEND_PORT.to_s }
|
|
20
|
+
output, error, status = Open3.capture3(env, RbConfig.ruby, '-I', lib, '-e', script)
|
|
21
|
+
expect(status.success?).to eq(true), error
|
|
22
|
+
expect(output).to eq("true")
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it "raises an error when no requested backend is usable" do
|
|
26
|
+
backend = RUBY_PLATFORM.include?("solaris") ? :kqueue : :port
|
|
27
|
+
expect {
|
|
28
|
+
Cool.io::Loop.new(skip_environment: true, backend: backend)
|
|
29
|
+
}.to raise_error(RuntimeError, /no usable backend/)
|
|
30
|
+
end
|
|
31
|
+
end
|
data/spec/stat_watcher_spec.rb
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require File.expand_path('../spec_helper', __FILE__)
|
|
2
|
+
require 'tmpdir'
|
|
2
3
|
|
|
3
4
|
TEMP_FILE_PATH = "./test.txt"
|
|
4
5
|
|
|
@@ -95,6 +96,60 @@ describe Cool.io::StatWatcher do
|
|
|
95
96
|
expect(watcher.accessed).to eq(true)
|
|
96
97
|
end
|
|
97
98
|
|
|
99
|
+
it "reports sizes and timestamps beyond the 32-bit range" do
|
|
100
|
+
Dir.mktmpdir do |dir|
|
|
101
|
+
path = File.join(dir, "large")
|
|
102
|
+
File.write(path, "a")
|
|
103
|
+
size = 2**32 + 1
|
|
104
|
+
time = Time.utc(2040, 1, 1)
|
|
105
|
+
current = nil
|
|
106
|
+
reactor = Cool.io::Loop.new
|
|
107
|
+
watcher = Cool.io::StatWatcher.new(path, INTERVAL)
|
|
108
|
+
watcher.define_singleton_method(:on_change) { |_, cur| current = cur }
|
|
109
|
+
watcher.attach(reactor)
|
|
110
|
+
|
|
111
|
+
File.truncate(path, size)
|
|
112
|
+
File.utime(time, time, path)
|
|
113
|
+
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + 5
|
|
114
|
+
until (current && current.size == size && current.mtime == time) ||
|
|
115
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC) > deadline
|
|
116
|
+
reactor.run_once(0.1)
|
|
117
|
+
end
|
|
118
|
+
watcher.detach
|
|
119
|
+
|
|
120
|
+
expect(current.size).to eq(size)
|
|
121
|
+
expect(current.mtime).to eq(time)
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
it "reports blksize and blocks as File.lstat does" do
|
|
126
|
+
Dir.mktmpdir do |dir|
|
|
127
|
+
path = File.join(dir, "file")
|
|
128
|
+
File.write(path, "a")
|
|
129
|
+
size = 8192
|
|
130
|
+
current = nil
|
|
131
|
+
reactor = Cool.io::Loop.new
|
|
132
|
+
watcher = Cool.io::StatWatcher.new(path, INTERVAL)
|
|
133
|
+
watcher.define_singleton_method(:on_change) { |_, cur| current = cur }
|
|
134
|
+
watcher.attach(reactor)
|
|
135
|
+
begin
|
|
136
|
+
File.write(path, "a" * size)
|
|
137
|
+
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + 5
|
|
138
|
+
until (current && current.size == size) ||
|
|
139
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC) > deadline
|
|
140
|
+
reactor.run_once(0.1)
|
|
141
|
+
end
|
|
142
|
+
ensure
|
|
143
|
+
watcher.detach
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
stat = File.lstat(path)
|
|
147
|
+
expect(current).not_to be_nil
|
|
148
|
+
expect(current.blksize).to eq(stat.blksize)
|
|
149
|
+
expect(current.blocks).to eq(stat.blocks)
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
98
153
|
it "raises ArgumentError when the path contains a null byte" do
|
|
99
154
|
expect { MyStatWatcher.new("foo\0bar") }.to raise_error(ArgumentError)
|
|
100
155
|
end
|
data/spec/tcp_server_spec.rb
CHANGED
|
@@ -175,9 +175,9 @@ describe Coolio::TCPServer do
|
|
|
175
175
|
it "connected socket called on_connect" do
|
|
176
176
|
begin
|
|
177
177
|
connected = false
|
|
178
|
-
server = Cool.io::TCPServer.new(
|
|
178
|
+
server = Cool.io::TCPServer.new(HOST, port, ServerOnConnect, proc { connected = true })
|
|
179
179
|
loop.attach server
|
|
180
|
-
s = TCPSocket.open(
|
|
180
|
+
s = TCPSocket.open(HOST, port)
|
|
181
181
|
loop.run_once
|
|
182
182
|
s.close
|
|
183
183
|
expect(connected).to eq true
|
|
@@ -201,9 +201,9 @@ describe Coolio::TCPServer do
|
|
|
201
201
|
it "closed socket called on_close" do
|
|
202
202
|
begin
|
|
203
203
|
closed = false
|
|
204
|
-
server = Cool.io::TCPServer.new(
|
|
204
|
+
server = Cool.io::TCPServer.new(HOST, port, ServerOnConnect, proc { closed = true })
|
|
205
205
|
loop.attach server
|
|
206
|
-
s = TCPSocket.open(
|
|
206
|
+
s = TCPSocket.open(HOST, port)
|
|
207
207
|
loop.run_once
|
|
208
208
|
s.close
|
|
209
209
|
loop.run_once
|
|
@@ -229,10 +229,10 @@ describe Coolio::TCPServer do
|
|
|
229
229
|
it "server socket received data" do
|
|
230
230
|
begin
|
|
231
231
|
data = "aaa"
|
|
232
|
-
server = Cool.io::TCPServer.new(
|
|
232
|
+
server = Cool.io::TCPServer.new(HOST, port, Echo, proc { |d| data = d })
|
|
233
233
|
loop.attach server
|
|
234
234
|
thread = Thread.new { loop.run }
|
|
235
|
-
s = TCPSocket.open(
|
|
235
|
+
s = TCPSocket.open(HOST, port)
|
|
236
236
|
s.write "zzz"
|
|
237
237
|
sleep 0.1
|
|
238
238
|
expect(data).to eq "zzz"
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
require File.expand_path('../spec_helper', __FILE__)
|
|
2
|
+
|
|
3
|
+
describe 'Watcher reattachment' do
|
|
4
|
+
[:timer, :io].each do |type|
|
|
5
|
+
[false, true].each do |disabled|
|
|
6
|
+
[false, true].each do |same_loop|
|
|
7
|
+
it "reattaches a #{disabled ? 'disabled' : 'enabled'} #{type} watcher to #{same_loop ? 'the same' : 'another'} loop" do
|
|
8
|
+
original = Coolio::Loop.new
|
|
9
|
+
destination = same_loop ? original : Coolio::Loop.new
|
|
10
|
+
if type == :io
|
|
11
|
+
server = TCPServer.new('127.0.0.1', 0)
|
|
12
|
+
writer = TCPSocket.new('127.0.0.1', server.addr[1])
|
|
13
|
+
reader = server.accept
|
|
14
|
+
server.close
|
|
15
|
+
watcher = Coolio::IOWatcher.new(reader)
|
|
16
|
+
else
|
|
17
|
+
watcher = Coolio::TimerWatcher.new(0.01, true)
|
|
18
|
+
end
|
|
19
|
+
fired = false
|
|
20
|
+
callback = proc { fired = true }
|
|
21
|
+
if type == :io
|
|
22
|
+
watcher.on_readable(&callback)
|
|
23
|
+
else
|
|
24
|
+
watcher.on_timer(&callback)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
watcher.attach(original)
|
|
28
|
+
watcher.disable if disabled
|
|
29
|
+
expect(watcher.attach(destination)).to equal(watcher)
|
|
30
|
+
expect(watcher.evloop).to equal(destination)
|
|
31
|
+
expect(watcher).to be_enabled
|
|
32
|
+
expect(destination.instance_variable_get(:@active_watchers)).to eq(1)
|
|
33
|
+
unless same_loop
|
|
34
|
+
expect(original.watchers).to be_empty
|
|
35
|
+
expect(original.instance_variable_get(:@active_watchers)).to eq(0)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
writer.write('a') if writer
|
|
39
|
+
sleep 0.02 if type == :timer
|
|
40
|
+
destination.run_once(0.1)
|
|
41
|
+
expect(fired).to eq(true)
|
|
42
|
+
watcher.detach
|
|
43
|
+
expect(destination.watchers).to be_empty
|
|
44
|
+
expect(destination.instance_variable_get(:@active_watchers)).to eq(0)
|
|
45
|
+
ensure
|
|
46
|
+
watcher.detach if watcher && watcher.attached?
|
|
47
|
+
reader.close if reader
|
|
48
|
+
writer.close if writer
|
|
49
|
+
server.close if server && !server.closed?
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
require File.expand_path('../spec_helper', __FILE__)
|
|
2
|
+
require 'tmpdir'
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
require 'rbconfig'
|
|
5
|
+
|
|
6
|
+
describe 'Windows stat notification internals', :if => RUBY_PLATFORM =~ /mingw|mswin/ do
|
|
7
|
+
before(:context) do
|
|
8
|
+
@build = Dir.mktmpdir('coolio-stat-native-')
|
|
9
|
+
source = File.expand_path('fixtures/windows_stat_native.c', __dir__)
|
|
10
|
+
libev = File.expand_path('../ext/libev', __dir__)
|
|
11
|
+
FileUtils.cp(source, @build)
|
|
12
|
+
File.write(File.join(@build, 'extconf.rb'), <<~RUBY)
|
|
13
|
+
begin
|
|
14
|
+
require 'devkit' if RUBY_PLATFORM =~ /mingw/
|
|
15
|
+
rescue LoadError
|
|
16
|
+
# Other Windows toolchains can provide the compiler through PATH.
|
|
17
|
+
end
|
|
18
|
+
require 'mkmf'
|
|
19
|
+
$INCFLAGS << ' -I"#{libev}"'
|
|
20
|
+
abort 'ws2_32 is required' unless have_library('ws2_32')
|
|
21
|
+
create_makefile('coolio_stat_native')
|
|
22
|
+
RUBY
|
|
23
|
+
File.open(File.join(@build, 'extconf.rb'), 'a') do |file|
|
|
24
|
+
file.write <<~'RUBY'
|
|
25
|
+
# Match cool.io's Windows link order so Winsock calls resolve to ws2_32.
|
|
26
|
+
contents = File.read('Makefile')
|
|
27
|
+
contents.gsub!(/LIBS = (.*) (\S*ws2_32\S*)/i) do
|
|
28
|
+
"LIBS = #{Regexp.last_match(2)} #{Regexp.last_match(1)}"
|
|
29
|
+
end
|
|
30
|
+
File.write('Makefile', contents)
|
|
31
|
+
make = ENV['MAKE'] || RbConfig::CONFIG['MAKE'] || (RUBY_PLATFORM =~ /mswin/ ? 'nmake' : 'make')
|
|
32
|
+
abort 'native stat test build failed' unless system(make)
|
|
33
|
+
RUBY
|
|
34
|
+
end
|
|
35
|
+
output = IO.popen([RbConfig.ruby, 'extconf.rb'], chdir: @build, err: [:child, :out], &:read)
|
|
36
|
+
raise output unless $?.success?
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
after(:context) do
|
|
40
|
+
FileUtils.remove_entry(@build) if @build && File.directory?(@build)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def native_result(script, *args)
|
|
44
|
+
error_log = File.join(@build, 'native-error.log')
|
|
45
|
+
command = [RbConfig.ruby, '-I', @build, '-rcoolio_stat_native', '-e',
|
|
46
|
+
"$stdout.binmode; #{script}", *args]
|
|
47
|
+
output = IO.popen(command, 'rb', err: error_log, &:read)
|
|
48
|
+
expect($?.success?).to eq(true), File.read(error_log)
|
|
49
|
+
Marshal.load(output)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def native_paths(paths)
|
|
53
|
+
args = paths.flat_map { |path, parent, _| [path, parent ? '1' : '0'] }
|
|
54
|
+
native_result(<<~'RUBY', *args)
|
|
55
|
+
paths = ARGV.each_slice(2).map { |path, parent| CoolioStatNativeSpec.path(path, parent == '1') }
|
|
56
|
+
$stdout.write(Marshal.dump(paths))
|
|
57
|
+
RUBY
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it 'preserves ordinary and extended-length drive roots' do
|
|
61
|
+
paths = [
|
|
62
|
+
['C:\\file.txt', true, 'C:\\'],
|
|
63
|
+
['C:\\', false, 'C:\\'],
|
|
64
|
+
['C:\\', true, 'C:\\'],
|
|
65
|
+
['\\\\?\\C:\\file.txt', true, '\\\\?\\C:\\'],
|
|
66
|
+
['\\\\?\\C:\\', false, '\\\\?\\C:\\'],
|
|
67
|
+
['\\\\?\\C:\\', true, '\\\\?\\C:\\'],
|
|
68
|
+
['\\\\?\\C:\\dir\\file', true, '\\\\?\\C:\\dir']
|
|
69
|
+
]
|
|
70
|
+
expect(native_paths(paths)).to eq(paths.map(&:last))
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it 'preserves ordinary and extended-length UNC share roots' do
|
|
74
|
+
paths = [
|
|
75
|
+
['\\\\server\\share\\', false, '\\\\server\\share\\'],
|
|
76
|
+
['\\\\server\\share\\', true, '\\\\server\\share\\'],
|
|
77
|
+
['\\\\server\\share\\file', true, '\\\\server\\share\\'],
|
|
78
|
+
['\\\\?\\UNC\\server\\share\\', false, '\\\\?\\UNC\\server\\share\\'],
|
|
79
|
+
['\\\\?\\UNC\\server\\share\\file', true, '\\\\?\\UNC\\server\\share\\']
|
|
80
|
+
]
|
|
81
|
+
expect(native_paths(paths)).to eq(paths.map(&:last))
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
it 'falls back to polling on pipe failure and recovers without creating a second pipe' do
|
|
85
|
+
path = File.join(@build, 'watched.txt')
|
|
86
|
+
result = native_result('$stdout.write(Marshal.dump(CoolioStatNativeSpec.check_pipe(ARGV[0])))', path)
|
|
87
|
+
expect(result).to eq(true)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
it 'destroys the loop without waiting for delayed worker completions' do
|
|
91
|
+
path = File.join(@build, 'shutdown.txt')
|
|
92
|
+
result = native_result('$stdout.write(Marshal.dump(CoolioStatNativeSpec.check_shutdown(ARGV[0])))', path)
|
|
93
|
+
expect(result).to eq(true)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
[[0, 'polling'], [1, 'notifications'], [2, 'polling after an unrelated name change']].each do |mode, description|
|
|
97
|
+
it "checks a shared directory once without alias lookups during #{description}" do
|
|
98
|
+
result = native_result(<<~'RUBY', @build, mode.to_s)
|
|
99
|
+
result = CoolioStatNativeSpec.path_work(ARGV[0], ARGV[1].to_i)
|
|
100
|
+
$stdout.write(Marshal.dump(result))
|
|
101
|
+
RUBY
|
|
102
|
+
# One open and two identity queries for 32 changed files; no alias work.
|
|
103
|
+
expect(result).to eq([1, 2, 0, true])
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
it 'keeps finding each watch as watchers stop, restart and re-register' do
|
|
108
|
+
result = native_result('$stdout.write(Marshal.dump(CoolioStatNativeSpec.watch_index(ARGV[0])))', @build)
|
|
109
|
+
expect(result).to eq([1, 2, 0, true, true])
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
it 'shares directories and routes name records through the directory index' do
|
|
113
|
+
result = native_result(<<~'RUBY', @build)
|
|
114
|
+
dir = File.join(ARGV[0], 'a')
|
|
115
|
+
Dir.mkdir(dir) unless Dir.exist?(dir)
|
|
116
|
+
File.write(File.join(dir, "\u00e9.txt"), 'a')
|
|
117
|
+
$stdout.write(Marshal.dump(CoolioStatNativeSpec.name_index(ARGV[0])))
|
|
118
|
+
RUBY
|
|
119
|
+
expect(result).to eq([true,
|
|
120
|
+
[false, true, false, true, false, true, true],
|
|
121
|
+
[true, true, false, true, false, true, true]])
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
it 'relinks changed alias names and unlinks them when the watcher stops' do
|
|
125
|
+
result = native_result('$stdout.write(Marshal.dump(CoolioStatNativeSpec.alias_index(ARGV[0])))', @build)
|
|
126
|
+
expect(result).to eq([true, false, true, true, true])
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
it 'skips name lookups for roots and wildcard names' do
|
|
130
|
+
result = native_result('$stdout.write(Marshal.dump(CoolioStatNativeSpec.name_guards(ARGV[0])))', @build)
|
|
131
|
+
expect(result).to eq(root: true, wildcard: true)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
it 'routes records for the short and the long name of a watched file' do
|
|
135
|
+
result = native_result('$stdout.write(Marshal.dump(CoolioStatNativeSpec.short_alias(ARGV[0])))', @build)
|
|
136
|
+
skip 'the volume does not create 8.3 names' if result.nil?
|
|
137
|
+
expect(result).to eq(short: true, long: true)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
it 'keeps sharing directories, queues and routing names after the tables grow' do
|
|
141
|
+
result = native_result('$stdout.write(Marshal.dump(CoolioStatNativeSpec.index_growth(ARGV[0])))', @build)
|
|
142
|
+
expect(result).to eq([true, true, true, true])
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
[[1, 'polling'], [0, 'an explicit registration']].each do |polling, description|
|
|
146
|
+
it "keeps a shared child directory when a relative directory moves with the current directory during #{description}" do
|
|
147
|
+
first, second = %w[cwd1 cwd2].map { |name| File.join(@build, "#{name}-#{polling}") }
|
|
148
|
+
FileUtils.mkdir_p(File.join(first, 'rel'))
|
|
149
|
+
File.write(File.join(first, 'rel', 'f.txt'), 'a')
|
|
150
|
+
FileUtils.mkdir_p(File.join(second, 'rel'))
|
|
151
|
+
script = '$stdout.write(Marshal.dump(CoolioStatNativeSpec.children_cwd(ARGV[0], ARGV[1], ARGV[2].to_i)))'
|
|
152
|
+
result = native_result(script, first, second, polling.to_s)
|
|
153
|
+
expect(result).to eq(shared: true, failed: false, moved: false, followed: true)
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def replaced_dir(mode)
|
|
158
|
+
script = '$stdout.write(Marshal.dump(CoolioStatNativeSpec.replaced_dir(ARGV[0], ARGV[1].to_i)))'
|
|
159
|
+
native_result(script, @build, mode.to_s)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
it 'moves directory and file watchers to a directory recreated under the same name' do
|
|
163
|
+
expect(replaced_dir(0)).to eq(directory: true, file: true)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
it 'moves the watchers when a rename record arrives before the creation record' do
|
|
167
|
+
expect(replaced_dir(1)).to eq(directory: true, file: true)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
it 'registers the contents of a directory that was missing while the watcher registered again' do
|
|
171
|
+
expect(replaced_dir(2)).to eq(directory: true, file: nil)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
it 'refreshes aliases after replacement records and retries failed lookups' do
|
|
175
|
+
path = File.join(@build, 'aliases.txt')
|
|
176
|
+
result = native_result('$stdout.write(Marshal.dump(CoolioStatNativeSpec.alias_recovery(ARGV[0])))', path)
|
|
177
|
+
expect(result).to eq([true, true, true, true])
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
it 'uses effective notification flags and respects EVFLAG_NOENV' do
|
|
181
|
+
path = File.join(@build, 'flags.txt')
|
|
182
|
+
# EVFLAG_NOINOTIFY = 0x00100000; EVFLAG_NOENV = 0x01000000.
|
|
183
|
+
[[0, 1048576, false], [16777216, 1048576, true],
|
|
184
|
+
[1048576, 0, true], [17825792, 0, false]].each do |flags, environment, expected|
|
|
185
|
+
result = native_result(<<~'RUBY', path, flags.to_s, environment.to_s)
|
|
186
|
+
ENV['LIBEV_FLAGS'] = ARGV[2]
|
|
187
|
+
enabled = CoolioStatNativeSpec.notification_enabled(ARGV[0], ARGV[1].to_i)
|
|
188
|
+
$stdout.write(Marshal.dump(enabled))
|
|
189
|
+
RUBY
|
|
190
|
+
expect(result).to eq(expected), "flags=#{flags}, LIBEV_FLAGS=#{environment}"
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|