filewatcher 1.1.1 → 2.0.0.beta5

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.
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Filewatcher
4
+ module SpecHelper
5
+ ## Base class for Filewatcher runners in specs
6
+ class WatchRun
7
+ include Filewatcher::SpecHelper
8
+
9
+ TMP_DIR = "#{Dir.getwd}/spec/tmp"
10
+
11
+ attr_reader :filename
12
+
13
+ def initialize(filename:, action:, directory:)
14
+ @filename =
15
+ if filename.match? %r{^(/|~|[A-Z]:)} then filename
16
+ else File.join(TMP_DIR, filename)
17
+ end
18
+ @directory = directory
19
+ @action = action
20
+ debug "action = #{action}"
21
+ end
22
+
23
+ def start
24
+ debug 'start'
25
+ File.write(@filename, 'content1') unless @action == :create
26
+
27
+ wait seconds: 1
28
+ end
29
+
30
+ def run(make_changes_times: 1)
31
+ start
32
+
33
+ make_changes_times.times do
34
+ make_changes
35
+
36
+ wait seconds: 2
37
+ end
38
+
39
+ stop
40
+ end
41
+
42
+ def stop
43
+ debug 'stop'
44
+ FileUtils.rm_r(@filename) if File.exist?(@filename)
45
+ end
46
+
47
+ private
48
+
49
+ def make_changes
50
+ debug "make changes, @action = #{@action}, @filename = #{@filename}"
51
+
52
+ if @action == :delete
53
+ FileUtils.remove(@filename)
54
+ elsif @directory
55
+ FileUtils.mkdir_p(@filename)
56
+ else
57
+ ## There is no `File.write` because of strange difference in parallel `File.mtime`
58
+ ## https://cirrus-ci.com/task/6107605053472768?command=test#L497-L511
59
+ system "echo 'content2' > #{@filename}"
60
+ debug_file_mtime
61
+ end
62
+
63
+ wait seconds: 1
64
+ end
65
+
66
+ def debug_file_mtime
67
+ debug "stat #{@filename}: #{system_stat(@filename)}"
68
+ debug "File.mtime = #{File.mtime(@filename).strftime('%F %T.%9N')}"
69
+ end
70
+ end
71
+ end
72
+ end
@@ -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.beta5'
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