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.
@@ -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