filewatcher 1.1.1 → 2.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../filewatcher'
4
-
5
3
  class Filewatcher
6
- VERSION = '1.1.1'.freeze
4
+ VERSION = '2.0.0.beta1'
7
5
  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
@@ -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