filewatcher 1.1.0 → 2.0.0.beta4

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