filewatcher 1.1.1 → 2.0.0.beta1
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 +5 -5
- data/lib/filewatcher.rb +49 -59
- data/lib/filewatcher/cycles.rb +21 -12
- data/lib/filewatcher/snapshot.rb +65 -0
- data/lib/filewatcher/snapshots.rb +56 -0
- data/lib/filewatcher/spec_helper.rb +66 -0
- data/lib/filewatcher/spec_helper/watch_run.rb +74 -0
- data/lib/filewatcher/version.rb +1 -3
- data/spec/filewatcher/snapshot_spec.rb +67 -0
- data/spec/filewatcher/version_spec.rb +11 -0
- data/spec/filewatcher_spec.rb +289 -0
- data/spec/spec_helper.rb +22 -0
- data/spec/spec_helper/ruby_watch_run.rb +82 -0
- metadata +80 -46
- data/bin/banner.txt +0 -17
- data/bin/filewatcher +0 -106
- 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 -238
- data/test/test_filewatcher.rb +0 -354
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,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../lib/filewatcher/version'
|
4
|
+
|
5
|
+
describe 'Filewatcher::VERSION' do
|
6
|
+
subject { Object.const_get(self.class.description) }
|
7
|
+
|
8
|
+
it { is_expected.to be_kind_of String }
|
9
|
+
|
10
|
+
it { is_expected.not_to be_empty }
|
11
|
+
end
|
@@ -0,0 +1,289 @@
|
|
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 '#initialize' do
|
52
|
+
describe 'regular run' do
|
53
|
+
before { watch_run.run }
|
54
|
+
|
55
|
+
context 'with excluding selected file patterns' do
|
56
|
+
let(:filewatcher) do
|
57
|
+
initialize_filewatcher(
|
58
|
+
File.expand_path('spec/tmp/**/*'),
|
59
|
+
exclude: File.expand_path('spec/tmp/**/*.txt')
|
60
|
+
)
|
61
|
+
end
|
62
|
+
|
63
|
+
it { is_expected.to be_empty }
|
64
|
+
end
|
65
|
+
|
66
|
+
context 'with absolute paths including globs' do
|
67
|
+
let(:filewatcher) do
|
68
|
+
initialize_filewatcher(
|
69
|
+
File.expand_path('spec/tmp/**/*')
|
70
|
+
)
|
71
|
+
end
|
72
|
+
|
73
|
+
it { is_expected.to eq [{ watch_run.filename => :updated }] }
|
74
|
+
end
|
75
|
+
|
76
|
+
context 'with globs' do
|
77
|
+
let(:filewatcher) { initialize_filewatcher('spec/tmp/**/*') }
|
78
|
+
|
79
|
+
it { is_expected.to eq [{ watch_run.filename => :updated }] }
|
80
|
+
end
|
81
|
+
|
82
|
+
context 'with explicit relative paths with globs' do
|
83
|
+
let(:filewatcher) { initialize_filewatcher('./spec/tmp/**/*') }
|
84
|
+
|
85
|
+
it { is_expected.to eq [{ watch_run.filename => :updated }] }
|
86
|
+
end
|
87
|
+
|
88
|
+
context 'with explicit relative paths' do
|
89
|
+
let(:filewatcher) { initialize_filewatcher('./spec/tmp') }
|
90
|
+
|
91
|
+
it { is_expected.to eq [{ watch_run.filename => :updated }] }
|
92
|
+
end
|
93
|
+
|
94
|
+
context 'with tilde expansion' do
|
95
|
+
let(:filename) { File.expand_path('~/file_watcher_1.txt') }
|
96
|
+
|
97
|
+
let(:filewatcher) { initialize_filewatcher('~/file_watcher_1.txt') }
|
98
|
+
|
99
|
+
it { is_expected.to eq [{ filename => :updated }] }
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
describe '`:immediate` option' do
|
104
|
+
before do
|
105
|
+
watch_run.start
|
106
|
+
watch_run.stop
|
107
|
+
end
|
108
|
+
|
109
|
+
context 'when is `true`' do
|
110
|
+
let(:immediate) { true }
|
111
|
+
|
112
|
+
it { is_expected.to eq [{ '' => '' }] }
|
113
|
+
|
114
|
+
describe 'when watched' do
|
115
|
+
subject { watch_run.watched }
|
116
|
+
|
117
|
+
it { is_expected.to be > 0 }
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
context 'when is `false`' do
|
122
|
+
let(:immediate) { false }
|
123
|
+
|
124
|
+
it { is_expected.to be_empty }
|
125
|
+
|
126
|
+
describe 'when watched' do
|
127
|
+
subject { watch_run.watched }
|
128
|
+
|
129
|
+
it { is_expected.to eq 0 }
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
describe '#watch' do
|
136
|
+
before do
|
137
|
+
FileUtils.mkdir_p subdirectory if defined? subdirectory
|
138
|
+
|
139
|
+
watch_run.run
|
140
|
+
end
|
141
|
+
|
142
|
+
describe 'detecting file deletions' do
|
143
|
+
let(:action) { :delete }
|
144
|
+
|
145
|
+
it { is_expected.to eq [{ watch_run.filename => :deleted }] }
|
146
|
+
end
|
147
|
+
|
148
|
+
context 'when there are file additions' do
|
149
|
+
let(:action) { :create }
|
150
|
+
|
151
|
+
it { is_expected.to eq [{ watch_run.filename => :created }] }
|
152
|
+
end
|
153
|
+
|
154
|
+
context 'when there are file updates' do
|
155
|
+
let(:action) { :update }
|
156
|
+
|
157
|
+
it { is_expected.to eq [{ watch_run.filename => :updated }] }
|
158
|
+
end
|
159
|
+
|
160
|
+
context 'when there are new files in subdirectories' do
|
161
|
+
let(:subdirectory) { File.expand_path('spec/tmp/new_sub_directory') }
|
162
|
+
|
163
|
+
let(:filename) { File.join(subdirectory, 'file.txt') }
|
164
|
+
let(:action) { :create }
|
165
|
+
let(:every) { true }
|
166
|
+
## https://github.com/filewatcher/filewatcher/pull/115#issuecomment-674581595
|
167
|
+
let(:interval) { 0.4 }
|
168
|
+
|
169
|
+
it do
|
170
|
+
expect(processed).to eq [
|
171
|
+
{ subdirectory => :updated, watch_run.filename => :created }
|
172
|
+
]
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
context 'when there are new subdirectories' do
|
177
|
+
let(:filename) { 'new_sub_directory' }
|
178
|
+
let(:directory) { true }
|
179
|
+
let(:action) { :create }
|
180
|
+
|
181
|
+
it { is_expected.to eq [{ watch_run.filename => :created }] }
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
describe '#stop' do
|
186
|
+
subject { watch_run.thread.join }
|
187
|
+
|
188
|
+
before do
|
189
|
+
watch_run.start
|
190
|
+
watch_run.filewatcher.stop
|
191
|
+
end
|
192
|
+
|
193
|
+
it { is_expected.to eq watch_run.thread }
|
194
|
+
end
|
195
|
+
|
196
|
+
def write_tmp_files(range)
|
197
|
+
logger.debug "#{__method__} #{range}"
|
198
|
+
|
199
|
+
directory = 'spec/tmp'
|
200
|
+
FileUtils.mkdir_p directory
|
201
|
+
|
202
|
+
range.to_a.map do |n|
|
203
|
+
File.write(file = "#{directory}/file#{n}.txt", "content#{n}")
|
204
|
+
|
205
|
+
Filewatcher::SpecHelper.wait seconds: 1
|
206
|
+
|
207
|
+
file
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
shared_context 'when paused' do
|
212
|
+
let(:action) { :create }
|
213
|
+
let(:every) { true }
|
214
|
+
|
215
|
+
before do
|
216
|
+
watch_run.start
|
217
|
+
|
218
|
+
logger.debug 'filewatcher.pause'
|
219
|
+
watch_run.filewatcher.pause
|
220
|
+
|
221
|
+
Filewatcher::SpecHelper.wait seconds: 1
|
222
|
+
|
223
|
+
write_tmp_files 1..4
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
describe '#pause' do
|
228
|
+
include_context 'when paused'
|
229
|
+
|
230
|
+
# update block should not have been called
|
231
|
+
it { is_expected.to be_empty }
|
232
|
+
end
|
233
|
+
|
234
|
+
describe '#resume' do
|
235
|
+
include_context 'when paused'
|
236
|
+
|
237
|
+
before do
|
238
|
+
logger.debug 'filewatcher.resume'
|
239
|
+
watch_run.filewatcher.resume
|
240
|
+
end
|
241
|
+
|
242
|
+
after do
|
243
|
+
watch_run.stop
|
244
|
+
end
|
245
|
+
|
246
|
+
describe 'changes while paused' do
|
247
|
+
# update block still should not have been called
|
248
|
+
it { is_expected.to be_empty }
|
249
|
+
end
|
250
|
+
|
251
|
+
describe 'changes after resumed' do
|
252
|
+
subject { processed_files }
|
253
|
+
|
254
|
+
let(:added_files) { write_tmp_files 5..7 }
|
255
|
+
|
256
|
+
before do
|
257
|
+
added_files
|
258
|
+
|
259
|
+
watch_run.wait
|
260
|
+
|
261
|
+
watch_run.filewatcher.stop
|
262
|
+
watch_run.stop
|
263
|
+
end
|
264
|
+
|
265
|
+
it { is_expected.to include_all_files added_files }
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
describe '#finalize' do
|
270
|
+
subject { processed_files }
|
271
|
+
|
272
|
+
let(:action) { :create }
|
273
|
+
let(:every) { true }
|
274
|
+
|
275
|
+
let(:added_files) { write_tmp_files 1..4 }
|
276
|
+
|
277
|
+
before do
|
278
|
+
watch_run.start
|
279
|
+
watch_run.filewatcher.stop
|
280
|
+
watch_run.thread.join
|
281
|
+
|
282
|
+
added_files
|
283
|
+
|
284
|
+
watch_run.filewatcher.finalize
|
285
|
+
end
|
286
|
+
|
287
|
+
it { is_expected.to include_all_files added_files }
|
288
|
+
end
|
289
|
+
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
|