filewatcher 0.5.4 → 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 +79 -138
- data/lib/filewatcher/cycles.rb +56 -0
- 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 +5 -0
- 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 +92 -42
- data/LICENSE +0 -20
- data/README.md +0 -269
- data/Rakefile +0 -19
- data/bin/filewatcher +0 -168
- data/test/fixtures/file1.txt +0 -1
- data/test/fixtures/file2.txt +0 -1
- data/test/fixtures/file3.rb +0 -1
- data/test/fixtures/file4.rb +0 -1
- data/test/fixtures/subdir/file5.rb +0 -1
- data/test/fixtures/subdir/file6.rb +0 -1
- data/test/test_filewatcher.rb +0 -181
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'pry-byebug'
|
7
|
+
rescue LoadError
|
8
|
+
nil
|
9
|
+
end
|
10
|
+
|
11
|
+
require_relative 'spec_helper/watch_run'
|
12
|
+
|
13
|
+
class Filewatcher
|
14
|
+
## Helper for common spec features between plugins
|
15
|
+
module SpecHelper
|
16
|
+
def logger
|
17
|
+
@logger ||= Logger.new($stdout, level: :debug)
|
18
|
+
end
|
19
|
+
|
20
|
+
def environment_specs_coefficients
|
21
|
+
@environment_specs_coefficients ||= {
|
22
|
+
-> { ENV['CI'] } => 1,
|
23
|
+
-> { RUBY_PLATFORM == 'java' } => 3,
|
24
|
+
-> { Gem::Platform.local.os == 'darwin' } => 1
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
def wait(seconds: 1, interval: 1, &block)
|
29
|
+
environment_specs_coefficients.each do |condition, coefficient|
|
30
|
+
next unless instance_exec(&condition)
|
31
|
+
|
32
|
+
interval *= coefficient
|
33
|
+
seconds *= coefficient
|
34
|
+
end
|
35
|
+
|
36
|
+
if block_given?
|
37
|
+
wait_with_block seconds, interval, &block
|
38
|
+
else
|
39
|
+
wait_without_block seconds
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def wait_with_block(seconds, interval, &_block)
|
44
|
+
(seconds / interval).ceil.times do
|
45
|
+
break if yield
|
46
|
+
|
47
|
+
debug "sleep interval #{interval}"
|
48
|
+
sleep interval
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def wait_without_block(seconds)
|
53
|
+
debug "sleep without intervals #{seconds}"
|
54
|
+
sleep seconds
|
55
|
+
end
|
56
|
+
|
57
|
+
def debug(string)
|
58
|
+
logger.debug "Thread ##{Thread.current.object_id} #{string}"
|
59
|
+
end
|
60
|
+
|
61
|
+
## https://github.com/rubocop-hq/ruby-style-guide/issues/556#issuecomment-691274359
|
62
|
+
# rubocop:disable Style/ModuleFunction
|
63
|
+
extend self
|
64
|
+
# rubocop:enable Style/ModuleFunction
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Filewatcher
|
4
|
+
module SpecHelper
|
5
|
+
## Base class for Filewatcher runners in specs
|
6
|
+
class WatchRun
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
TMP_DIR = "#{Dir.getwd}/spec/tmp"
|
10
|
+
|
11
|
+
def_delegators Filewatcher::SpecHelper, :debug, :wait
|
12
|
+
|
13
|
+
attr_reader :filename
|
14
|
+
|
15
|
+
def initialize(filename:, action:, directory:)
|
16
|
+
@filename =
|
17
|
+
if filename.match? %r{^(/|~|[A-Z]:)} then filename
|
18
|
+
else File.join(TMP_DIR, filename)
|
19
|
+
end
|
20
|
+
@directory = directory
|
21
|
+
@action = action
|
22
|
+
debug "action = #{action}"
|
23
|
+
end
|
24
|
+
|
25
|
+
def start
|
26
|
+
debug 'start'
|
27
|
+
File.write(@filename, 'content1') unless @action == :create
|
28
|
+
|
29
|
+
wait seconds: 1
|
30
|
+
end
|
31
|
+
|
32
|
+
def run(make_changes_times: 1)
|
33
|
+
start
|
34
|
+
|
35
|
+
make_changes_times.times do
|
36
|
+
make_changes
|
37
|
+
|
38
|
+
wait seconds: 2
|
39
|
+
end
|
40
|
+
|
41
|
+
stop
|
42
|
+
end
|
43
|
+
|
44
|
+
def stop
|
45
|
+
debug 'stop'
|
46
|
+
FileUtils.rm_r(@filename) if File.exist?(@filename)
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def make_changes
|
52
|
+
debug "make changes, @action = #{@action}, @filename = #{@filename}"
|
53
|
+
|
54
|
+
if @action == :delete
|
55
|
+
FileUtils.remove(@filename)
|
56
|
+
elsif @directory
|
57
|
+
FileUtils.mkdir_p(@filename)
|
58
|
+
else
|
59
|
+
## There is no `File.write` because of strange difference in parallel `File.mtime`
|
60
|
+
## https://cirrus-ci.com/task/6107605053472768?command=test#L497-L511
|
61
|
+
system "echo 'content2' > #{@filename}"
|
62
|
+
debug_file_mtime
|
63
|
+
end
|
64
|
+
|
65
|
+
wait seconds: 1
|
66
|
+
end
|
67
|
+
|
68
|
+
def debug_file_mtime
|
69
|
+
debug "stat #{@filename}: #{Filewatcher.system_stat(@filename)}"
|
70
|
+
debug "File.mtime = #{File.mtime(@filename).strftime('%F %T.%9N')}"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -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
|