filewatcher 1.1.0 → 2.0.0.beta4

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.0'.freeze
4
+ VERSION = '2.0.0.beta4'
7
5
  end
@@ -0,0 +1,68 @@
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
+ ## 1 second or more: https://github.com/oracle/truffleruby/issues/2337
53
+ sleep 1
54
+
55
+ File.write tmp_files[1], 'new content'
56
+ File.delete tmp_files[2]
57
+ File.write(new_file, 'new file')
58
+ end
59
+
60
+ it do
61
+ expect(difference).to eq(
62
+ tmp_files[1] => :updated,
63
+ tmp_files[2] => :deleted,
64
+ new_file => :created
65
+ )
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,9 @@
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 match(/^\d+\.\d+\.\d+(\.\w+(\.\d+)?)?$/) }
9
+ 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|truffle)?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
@@ -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