filewatcher 1.0.1 → 2.0.0.beta3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/filewatcher.rb +52 -63
- data/lib/filewatcher/cycles.rb +21 -12
- data/lib/filewatcher/snapshot.rb +64 -0
- data/lib/filewatcher/snapshots.rb +57 -0
- data/lib/filewatcher/spec_helper.rb +66 -0
- data/lib/filewatcher/spec_helper/watch_run.rb +76 -0
- data/lib/filewatcher/version.rb +1 -3
- data/spec/filewatcher/snapshot_spec.rb +67 -0
- data/spec/filewatcher/version_spec.rb +9 -0
- data/spec/filewatcher_spec.rb +302 -0
- data/spec/spec_helper.rb +22 -0
- data/spec/spec_helper/ruby_watch_run.rb +82 -0
- metadata +94 -50
- data/bin/banner.txt +0 -17
- data/bin/filewatcher +0 -105
- data/lib/filewatcher/env.rb +0 -33
- data/lib/filewatcher/runner.rb +0 -33
- data/test/dumpers/env_dumper.rb +0 -10
- data/test/dumpers/watched_dumper.rb +0 -5
- data/test/filewatcher/test_env.rb +0 -70
- data/test/filewatcher/test_runner.rb +0 -75
- data/test/filewatcher/test_version.rb +0 -13
- data/test/helper.rb +0 -216
- data/test/test_filewatcher.rb +0 -340
data/lib/filewatcher/version.rb
CHANGED
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../lib/filewatcher/snapshot'
|
4
|
+
|
5
|
+
describe Filewatcher::Snapshot do
|
6
|
+
let(:tmp_dir) { Filewatcher::SpecHelper::WatchRun::TMP_DIR }
|
7
|
+
|
8
|
+
let(:tmp_files) do
|
9
|
+
(1..4).map do |n|
|
10
|
+
File.join(tmp_dir, "file#{n}.txt")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize_snapshot
|
15
|
+
described_class.new Dir[
|
16
|
+
File.join(tmp_dir, '**', '*')
|
17
|
+
]
|
18
|
+
end
|
19
|
+
|
20
|
+
before do
|
21
|
+
FileUtils.mkdir_p tmp_dir
|
22
|
+
|
23
|
+
tmp_files.each_with_index do |tmp_file, n|
|
24
|
+
File.write tmp_file, "content#{n}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
after do
|
29
|
+
FileUtils.rm_r tmp_dir
|
30
|
+
end
|
31
|
+
|
32
|
+
describe '#initialize' do
|
33
|
+
subject(:snapshot) { initialize_snapshot }
|
34
|
+
|
35
|
+
it do
|
36
|
+
snapshot.each do |filename, snapshot_file|
|
37
|
+
expect(snapshot_file.mtime).to eq File.mtime(filename)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe '#-' do
|
43
|
+
subject(:difference) { second_snapshot - first_snapshot }
|
44
|
+
|
45
|
+
let(:first_snapshot) { initialize_snapshot }
|
46
|
+
let(:second_snapshot) { initialize_snapshot }
|
47
|
+
let(:new_file) { File.join(tmp_dir, 'file5.txt') }
|
48
|
+
|
49
|
+
before do
|
50
|
+
first_snapshot
|
51
|
+
|
52
|
+
sleep 0.1
|
53
|
+
|
54
|
+
File.write tmp_files[1], 'new content'
|
55
|
+
File.delete tmp_files[2]
|
56
|
+
File.write(new_file, 'new file')
|
57
|
+
end
|
58
|
+
|
59
|
+
it do
|
60
|
+
expect(difference).to eq(
|
61
|
+
tmp_files[1] => :updated,
|
62
|
+
tmp_files[2] => :deleted,
|
63
|
+
new_file => :created
|
64
|
+
)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,302 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
require_relative '../lib/filewatcher'
|
5
|
+
|
6
|
+
describe Filewatcher do
|
7
|
+
subject(:processed) { watch_run.processed }
|
8
|
+
|
9
|
+
before do
|
10
|
+
FileUtils.mkdir_p tmp_dir
|
11
|
+
end
|
12
|
+
|
13
|
+
after do
|
14
|
+
logger.debug "FileUtils.rm_r #{tmp_dir}"
|
15
|
+
FileUtils.rm_r tmp_dir
|
16
|
+
|
17
|
+
Filewatcher::SpecHelper.wait seconds: 5, interval: 0.2 do
|
18
|
+
!File.exist?(tmp_dir)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize_filewatcher(path, options = {})
|
23
|
+
described_class.new(path, options.merge(logger: logger))
|
24
|
+
end
|
25
|
+
|
26
|
+
let(:tmp_dir) { Filewatcher::SpecHelper::WatchRun::TMP_DIR }
|
27
|
+
let(:logger) { Filewatcher::SpecHelper.logger }
|
28
|
+
|
29
|
+
let(:filename) { 'tmp_file.txt' }
|
30
|
+
let(:action) { :update }
|
31
|
+
let(:directory) { false }
|
32
|
+
## TODO: Check its needless
|
33
|
+
let(:every) { false }
|
34
|
+
let(:immediate) { false }
|
35
|
+
let(:interval) { 0.2 }
|
36
|
+
let(:filewatcher) do
|
37
|
+
initialize_filewatcher(
|
38
|
+
File.join(tmp_dir, '**', '*'), interval: interval, every: every, immediate: immediate
|
39
|
+
)
|
40
|
+
end
|
41
|
+
|
42
|
+
let(:watch_run) do
|
43
|
+
Filewatcher::SpecHelper::RubyWatchRun.new(
|
44
|
+
filename: filename, filewatcher: filewatcher, action: action,
|
45
|
+
directory: directory
|
46
|
+
)
|
47
|
+
end
|
48
|
+
|
49
|
+
let(:processed_files) { watch_run.processed.flat_map(&:keys) }
|
50
|
+
|
51
|
+
describe '.print_version' do
|
52
|
+
subject(:method_call) { described_class.print_version }
|
53
|
+
|
54
|
+
let(:ruby_version_regexp) { 'j?ruby \d+\.\d+\.\d+.*' }
|
55
|
+
let(:filewatcher_version_regexp) { "Filewatcher #{Filewatcher::VERSION}" }
|
56
|
+
|
57
|
+
it do
|
58
|
+
expect { method_call }.to output(
|
59
|
+
/^#{ruby_version_regexp}\n#{filewatcher_version_regexp}$/
|
60
|
+
).to_stdout_from_any_process
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe '#initialize' do
|
65
|
+
describe 'regular run' do
|
66
|
+
before { watch_run.run }
|
67
|
+
|
68
|
+
context 'with excluding selected file patterns' do
|
69
|
+
let(:filewatcher) do
|
70
|
+
initialize_filewatcher(
|
71
|
+
File.expand_path('spec/tmp/**/*'),
|
72
|
+
exclude: File.expand_path('spec/tmp/**/*.txt')
|
73
|
+
)
|
74
|
+
end
|
75
|
+
|
76
|
+
it { is_expected.to be_empty }
|
77
|
+
end
|
78
|
+
|
79
|
+
context 'with absolute paths including globs' do
|
80
|
+
let(:filewatcher) do
|
81
|
+
initialize_filewatcher(
|
82
|
+
File.expand_path('spec/tmp/**/*')
|
83
|
+
)
|
84
|
+
end
|
85
|
+
|
86
|
+
it { is_expected.to eq [{ watch_run.filename => :updated }] }
|
87
|
+
end
|
88
|
+
|
89
|
+
context 'with globs' do
|
90
|
+
let(:filewatcher) { initialize_filewatcher('spec/tmp/**/*') }
|
91
|
+
|
92
|
+
it { is_expected.to eq [{ watch_run.filename => :updated }] }
|
93
|
+
end
|
94
|
+
|
95
|
+
context 'with explicit relative paths with globs' do
|
96
|
+
let(:filewatcher) { initialize_filewatcher('./spec/tmp/**/*') }
|
97
|
+
|
98
|
+
it { is_expected.to eq [{ watch_run.filename => :updated }] }
|
99
|
+
end
|
100
|
+
|
101
|
+
context 'with explicit relative paths' do
|
102
|
+
let(:filewatcher) { initialize_filewatcher('./spec/tmp') }
|
103
|
+
|
104
|
+
it { is_expected.to eq [{ watch_run.filename => :updated }] }
|
105
|
+
end
|
106
|
+
|
107
|
+
context 'with tilde expansion' do
|
108
|
+
let(:filename) { File.expand_path('~/file_watcher_1.txt') }
|
109
|
+
|
110
|
+
let(:filewatcher) { initialize_filewatcher('~/file_watcher_1.txt') }
|
111
|
+
|
112
|
+
it { is_expected.to eq [{ filename => :updated }] }
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
describe '`:immediate` option' do
|
117
|
+
before do
|
118
|
+
watch_run.start
|
119
|
+
watch_run.stop
|
120
|
+
end
|
121
|
+
|
122
|
+
context 'when is `true`' do
|
123
|
+
let(:immediate) { true }
|
124
|
+
|
125
|
+
it { is_expected.to eq [{ '' => '' }] }
|
126
|
+
|
127
|
+
describe 'when watched' do
|
128
|
+
subject { watch_run.watched }
|
129
|
+
|
130
|
+
it { is_expected.to be > 0 }
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
context 'when is `false`' do
|
135
|
+
let(:immediate) { false }
|
136
|
+
|
137
|
+
it { is_expected.to be_empty }
|
138
|
+
|
139
|
+
describe 'when watched' do
|
140
|
+
subject { watch_run.watched }
|
141
|
+
|
142
|
+
it { is_expected.to eq 0 }
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
describe '#watch' do
|
149
|
+
before do
|
150
|
+
FileUtils.mkdir_p subdirectory if defined? subdirectory
|
151
|
+
|
152
|
+
watch_run.run
|
153
|
+
end
|
154
|
+
|
155
|
+
describe 'detecting file deletions' do
|
156
|
+
let(:action) { :delete }
|
157
|
+
|
158
|
+
it { is_expected.to eq [{ watch_run.filename => :deleted }] }
|
159
|
+
end
|
160
|
+
|
161
|
+
context 'when there are file additions' do
|
162
|
+
let(:action) { :create }
|
163
|
+
|
164
|
+
it { is_expected.to eq [{ watch_run.filename => :created }] }
|
165
|
+
end
|
166
|
+
|
167
|
+
context 'when there are file updates' do
|
168
|
+
let(:action) { :update }
|
169
|
+
|
170
|
+
it { is_expected.to eq [{ watch_run.filename => :updated }] }
|
171
|
+
end
|
172
|
+
|
173
|
+
context 'when there are new files in subdirectories' do
|
174
|
+
let(:subdirectory) { File.expand_path('spec/tmp/new_sub_directory') }
|
175
|
+
|
176
|
+
let(:filename) { File.join(subdirectory, 'file.txt') }
|
177
|
+
let(:action) { :create }
|
178
|
+
let(:every) { true }
|
179
|
+
## https://github.com/filewatcher/filewatcher/pull/115#issuecomment-674581595
|
180
|
+
let(:interval) { 0.4 }
|
181
|
+
|
182
|
+
it do
|
183
|
+
expect(processed).to eq [
|
184
|
+
{ subdirectory => :updated, watch_run.filename => :created }
|
185
|
+
]
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
context 'when there are new subdirectories' do
|
190
|
+
let(:filename) { 'new_sub_directory' }
|
191
|
+
let(:directory) { true }
|
192
|
+
let(:action) { :create }
|
193
|
+
|
194
|
+
it { is_expected.to eq [{ watch_run.filename => :created }] }
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
describe '#stop' do
|
199
|
+
subject { watch_run.thread.join }
|
200
|
+
|
201
|
+
before do
|
202
|
+
watch_run.start
|
203
|
+
watch_run.filewatcher.stop
|
204
|
+
end
|
205
|
+
|
206
|
+
it { is_expected.to eq watch_run.thread }
|
207
|
+
end
|
208
|
+
|
209
|
+
def write_tmp_files(range)
|
210
|
+
logger.debug "#{__method__} #{range}"
|
211
|
+
|
212
|
+
directory = 'spec/tmp'
|
213
|
+
FileUtils.mkdir_p directory
|
214
|
+
|
215
|
+
range.to_a.map do |n|
|
216
|
+
File.write(file = "#{directory}/file#{n}.txt", "content#{n}")
|
217
|
+
|
218
|
+
Filewatcher::SpecHelper.wait seconds: 1
|
219
|
+
|
220
|
+
file
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
shared_context 'when paused' do
|
225
|
+
let(:action) { :create }
|
226
|
+
let(:every) { true }
|
227
|
+
|
228
|
+
before do
|
229
|
+
watch_run.start
|
230
|
+
|
231
|
+
logger.debug 'filewatcher.pause'
|
232
|
+
watch_run.filewatcher.pause
|
233
|
+
|
234
|
+
Filewatcher::SpecHelper.wait seconds: 1
|
235
|
+
|
236
|
+
write_tmp_files 1..4
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
describe '#pause' do
|
241
|
+
include_context 'when paused'
|
242
|
+
|
243
|
+
# update block should not have been called
|
244
|
+
it { is_expected.to be_empty }
|
245
|
+
end
|
246
|
+
|
247
|
+
describe '#resume' do
|
248
|
+
include_context 'when paused'
|
249
|
+
|
250
|
+
before do
|
251
|
+
logger.debug 'filewatcher.resume'
|
252
|
+
watch_run.filewatcher.resume
|
253
|
+
end
|
254
|
+
|
255
|
+
after do
|
256
|
+
watch_run.stop
|
257
|
+
end
|
258
|
+
|
259
|
+
describe 'changes while paused' do
|
260
|
+
# update block still should not have been called
|
261
|
+
it { is_expected.to be_empty }
|
262
|
+
end
|
263
|
+
|
264
|
+
describe 'changes after resumed' do
|
265
|
+
subject { processed_files }
|
266
|
+
|
267
|
+
let(:added_files) { write_tmp_files 5..7 }
|
268
|
+
|
269
|
+
before do
|
270
|
+
added_files
|
271
|
+
|
272
|
+
watch_run.wait
|
273
|
+
|
274
|
+
watch_run.filewatcher.stop
|
275
|
+
watch_run.stop
|
276
|
+
end
|
277
|
+
|
278
|
+
it { is_expected.to include_all_files added_files }
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
describe '#finalize' do
|
283
|
+
subject { processed_files }
|
284
|
+
|
285
|
+
let(:action) { :create }
|
286
|
+
let(:every) { true }
|
287
|
+
|
288
|
+
let(:added_files) { write_tmp_files 1..4 }
|
289
|
+
|
290
|
+
before do
|
291
|
+
watch_run.start
|
292
|
+
watch_run.filewatcher.stop
|
293
|
+
watch_run.thread.join
|
294
|
+
|
295
|
+
added_files
|
296
|
+
|
297
|
+
watch_run.filewatcher.finalize
|
298
|
+
end
|
299
|
+
|
300
|
+
it { is_expected.to include_all_files added_files }
|
301
|
+
end
|
302
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'simplecov'
|
4
|
+
SimpleCov.start
|
5
|
+
|
6
|
+
if ENV['CODECOV_TOKEN']
|
7
|
+
require 'codecov'
|
8
|
+
SimpleCov.formatter = SimpleCov::Formatter::Codecov
|
9
|
+
end
|
10
|
+
|
11
|
+
require_relative '../lib/filewatcher/spec_helper'
|
12
|
+
|
13
|
+
require_relative 'spec_helper/ruby_watch_run'
|
14
|
+
|
15
|
+
## For case when required from dumpers
|
16
|
+
if Object.const_defined?(:RSpec)
|
17
|
+
RSpec::Matchers.define :include_all_files do |expected|
|
18
|
+
match do |actual|
|
19
|
+
expected.all? { |file| actual.include? File.expand_path(file) }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Filewatcher
|
4
|
+
module SpecHelper
|
5
|
+
class RubyWatchRun < WatchRun
|
6
|
+
attr_reader :filewatcher, :thread, :watched, :processed
|
7
|
+
|
8
|
+
def initialize(filewatcher:, **args)
|
9
|
+
super(**args)
|
10
|
+
@filewatcher = filewatcher
|
11
|
+
|
12
|
+
@mutex = Mutex.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def start
|
16
|
+
super
|
17
|
+
@thread = thread_initialize
|
18
|
+
# thread needs a chance to start
|
19
|
+
wait seconds: 1
|
20
|
+
wait do
|
21
|
+
keep_watching = filewatcher.keep_watching
|
22
|
+
debug "keep_watching = #{keep_watching}"
|
23
|
+
keep_watching
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def stop
|
28
|
+
thread.exit
|
29
|
+
|
30
|
+
wait do
|
31
|
+
thread.stop?
|
32
|
+
end
|
33
|
+
|
34
|
+
super
|
35
|
+
end
|
36
|
+
|
37
|
+
def wait(seconds: 1)
|
38
|
+
super seconds: seconds, interval: filewatcher.interval
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def make_changes
|
44
|
+
super
|
45
|
+
|
46
|
+
# Some OS, file systems and Ruby interpretators
|
47
|
+
# doesn't catch milliseconds of `File.mtime`
|
48
|
+
wait do
|
49
|
+
@mutex.synchronize do
|
50
|
+
debug "processed = #{processed}"
|
51
|
+
debug "processed.any? = #{processed.any?}"
|
52
|
+
processed.any?
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def thread_initialize
|
58
|
+
@watched ||= 0
|
59
|
+
@processed = []
|
60
|
+
Thread.new { setup_filewatcher }
|
61
|
+
end
|
62
|
+
|
63
|
+
def setup_filewatcher
|
64
|
+
debug 'setup_filewatcher'
|
65
|
+
debug filewatcher.inspect
|
66
|
+
filewatcher.watch do |changes|
|
67
|
+
debug filewatcher.inspect
|
68
|
+
@mutex.synchronize do
|
69
|
+
debug "watch callback: changes = #{changes.inspect}"
|
70
|
+
increment_watched
|
71
|
+
@processed.push(changes)
|
72
|
+
# debug 'pushed to processed'
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def increment_watched
|
78
|
+
@watched += 1
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|