sass 3.3.0.rc.1 → 3.3.0.rc.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. data/Rakefile +1 -1
  2. data/VERSION +1 -1
  3. data/VERSION_DATE +1 -1
  4. data/lib/sass.rb +5 -0
  5. data/lib/sass/engine.rb +3 -5
  6. data/lib/sass/plugin.rb +0 -1
  7. data/lib/sass/plugin/compiler.rb +1 -2
  8. data/lib/sass/script/functions.rb +16 -2
  9. data/lib/sass/script/lexer.rb +22 -12
  10. data/lib/sass/script/parser.rb +27 -14
  11. data/lib/sass/script/tree/variable.rb +1 -1
  12. data/lib/sass/script/value/base.rb +1 -1
  13. data/lib/sass/script/value/color.rb +29 -17
  14. data/lib/sass/script/value/list.rb +1 -1
  15. data/lib/sass/script/value/number.rb +8 -1
  16. data/lib/sass/scss/parser.rb +2 -2
  17. data/lib/sass/selector/sequence.rb +18 -19
  18. data/lib/sass/selector/simple_sequence.rb +5 -5
  19. data/lib/sass/source/map.rb +1 -1
  20. data/lib/sass/tree/node.rb +25 -0
  21. data/lib/sass/tree/variable_node.rb +5 -0
  22. data/lib/sass/tree/visitors/base.rb +4 -7
  23. data/lib/sass/tree/visitors/check_nesting.rb +2 -2
  24. data/lib/sass/tree/visitors/perform.rb +12 -7
  25. data/lib/sass/util.rb +95 -50
  26. data/lib/sass/util/normalized_map.rb +63 -14
  27. data/lib/sass/util/ordered_hash.rb +9 -5
  28. data/lib/sass/version.rb +10 -12
  29. data/test/sass/engine_test.rb +37 -0
  30. data/test/sass/functions_test.rb +9 -2
  31. data/test/sass/importer_test.rb +3 -3
  32. data/test/sass/script_test.rb +12 -10
  33. data/test/sass/source_map_test.rb +8 -8
  34. data/test/sass/util/normalized_map_test.rb +22 -1
  35. data/test/sass/util_test.rb +18 -0
  36. data/test/test_helper.rb +16 -0
  37. data/vendor/listen/CHANGELOG.md +228 -0
  38. data/vendor/listen/CONTRIBUTING.md +38 -0
  39. data/vendor/listen/Gemfile +30 -0
  40. data/vendor/listen/Guardfile +8 -0
  41. data/vendor/listen/LICENSE +20 -0
  42. data/vendor/listen/README.md +315 -0
  43. data/vendor/listen/Rakefile +47 -0
  44. data/vendor/listen/Vagrantfile +96 -0
  45. data/vendor/listen/lib/listen.rb +40 -0
  46. data/vendor/listen/lib/listen/adapter.rb +214 -0
  47. data/vendor/listen/lib/listen/adapters/bsd.rb +112 -0
  48. data/vendor/listen/lib/listen/adapters/darwin.rb +85 -0
  49. data/vendor/listen/lib/listen/adapters/linux.rb +113 -0
  50. data/vendor/listen/lib/listen/adapters/polling.rb +67 -0
  51. data/vendor/listen/lib/listen/adapters/windows.rb +87 -0
  52. data/vendor/listen/lib/listen/dependency_manager.rb +126 -0
  53. data/vendor/listen/lib/listen/directory_record.rb +371 -0
  54. data/vendor/listen/lib/listen/listener.rb +225 -0
  55. data/vendor/listen/lib/listen/multi_listener.rb +143 -0
  56. data/vendor/listen/lib/listen/turnstile.rb +28 -0
  57. data/vendor/listen/lib/listen/version.rb +3 -0
  58. data/vendor/listen/listen.gemspec +22 -0
  59. data/vendor/listen/spec/listen/adapter_spec.rb +183 -0
  60. data/vendor/listen/spec/listen/adapters/bsd_spec.rb +36 -0
  61. data/vendor/listen/spec/listen/adapters/darwin_spec.rb +37 -0
  62. data/vendor/listen/spec/listen/adapters/linux_spec.rb +47 -0
  63. data/vendor/listen/spec/listen/adapters/polling_spec.rb +68 -0
  64. data/vendor/listen/spec/listen/adapters/windows_spec.rb +30 -0
  65. data/vendor/listen/spec/listen/dependency_manager_spec.rb +107 -0
  66. data/vendor/listen/spec/listen/directory_record_spec.rb +1225 -0
  67. data/vendor/listen/spec/listen/listener_spec.rb +169 -0
  68. data/vendor/listen/spec/listen/multi_listener_spec.rb +174 -0
  69. data/vendor/listen/spec/listen/turnstile_spec.rb +56 -0
  70. data/vendor/listen/spec/listen_spec.rb +73 -0
  71. data/vendor/listen/spec/spec_helper.rb +21 -0
  72. data/vendor/listen/spec/support/adapter_helper.rb +629 -0
  73. data/vendor/listen/spec/support/directory_record_helper.rb +55 -0
  74. data/vendor/listen/spec/support/fixtures_helper.rb +29 -0
  75. data/vendor/listen/spec/support/listeners_helper.rb +156 -0
  76. data/vendor/listen/spec/support/platform_helper.rb +15 -0
  77. metadata +318 -300
  78. data/test/Gemfile.lock +0 -10
@@ -0,0 +1,47 @@
1
+ require 'spec_helper'
2
+
3
+ describe Listen::Adapters::Linux do
4
+ if linux?
5
+ if Listen::Adapters::Linux.usable?
6
+ it "is usable on Linux" do
7
+ described_class.should be_usable
8
+ end
9
+
10
+ it_should_behave_like 'a filesystem adapter'
11
+ it_should_behave_like 'an adapter that call properly listener#on_change'
12
+
13
+ describe '#initialize' do
14
+ context 'when the inotify limit for watched files is not enough' do
15
+ before { INotify::Notifier.any_instance.should_receive(:watch).and_raise(Errno::ENOSPC) }
16
+
17
+ it 'fails gracefully' do
18
+ described_class.any_instance.should_receive(:abort).with(described_class::INOTIFY_LIMIT_MESSAGE)
19
+ described_class.new(File.dirname(__FILE__))
20
+ end
21
+ end
22
+ end
23
+ else
24
+ it "isn't usable on Linux with #{RbConfig::CONFIG['RUBY_INSTALL_NAME']}" do
25
+ described_class.should_not be_usable
26
+ end
27
+ end
28
+ end
29
+
30
+ if bsd?
31
+ it "isn't usable on BSD" do
32
+ described_class.should_not be_usable
33
+ end
34
+ end
35
+
36
+ if mac?
37
+ it "isn't usable on Mac OS X" do
38
+ described_class.should_not be_usable
39
+ end
40
+ end
41
+
42
+ if windows?
43
+ it "isn't usable on Windows" do
44
+ described_class.should_not be_usable
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,68 @@
1
+ require 'spec_helper'
2
+
3
+ describe Listen::Adapters::Polling do
4
+ subject { described_class.new('dir') }
5
+
6
+ it_should_behave_like 'a filesystem adapter'
7
+
8
+ describe '#initialize' do
9
+ it 'sets the latency to the default polling one' do
10
+ subject.latency.should eq Listen::Adapters::DEFAULT_POLLING_LATENCY
11
+ end
12
+ end
13
+
14
+ describe '#poll' do
15
+ let(:listener) { mock(Listen::Listener) }
16
+ let(:callback) { lambda { |changed_dirs, options| @called = true; listener.on_change(changed_dirs, options) } }
17
+
18
+ after { subject.stop }
19
+
20
+ context 'with one directory to watch' do
21
+ subject { Listen::Adapters::Polling.new('dir', {}, &callback) }
22
+
23
+ it 'calls listener.on_change' do
24
+ listener.should_receive(:on_change).at_least(1).times.with(['dir'], :recursive => true)
25
+ subject.start(false)
26
+ subject.wait_for_callback
27
+ end
28
+
29
+ it 'calls listener.on_change continuously' do
30
+ subject.latency = 0.001
31
+ listener.should_receive(:on_change).at_least(10).times.with(['dir'], :recursive => true)
32
+ subject.start(false)
33
+ 10.times { subject.wait_for_callback }
34
+ end
35
+
36
+ it "doesn't call listener.on_change if paused" do
37
+ subject.paused = true
38
+ subject.start(false)
39
+ subject.wait_for_callback
40
+ @called.should be_nil
41
+ end
42
+ end
43
+
44
+ context 'with multiple directories to watch' do
45
+ subject { Listen::Adapters::Polling.new(%w{dir1 dir2}, {}, &callback) }
46
+
47
+ it 'calls listener.on_change' do
48
+ listener.should_receive(:on_change).at_least(1).times.with(%w{dir1 dir2}, :recursive => true)
49
+ subject.start(false)
50
+ subject.wait_for_callback
51
+ end
52
+
53
+ it 'calls listener.on_change continuously' do
54
+ subject.latency = 0.001
55
+ listener.should_receive(:on_change).at_least(10).times.with(%w{dir1 dir2}, :recursive => true)
56
+ subject.start(false)
57
+ 10.times { subject.wait_for_callback }
58
+ end
59
+
60
+ it "doesn't call listener.on_change if paused" do
61
+ subject.paused = true
62
+ subject.start(false)
63
+ subject.wait_for_callback
64
+ @called.should be_nil
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,30 @@
1
+ require 'spec_helper'
2
+
3
+ describe Listen::Adapters::Windows do
4
+ if windows? && Listen::Adapters::Windows.usable?
5
+ it "is usable on Windows" do
6
+ described_class.should be_usable
7
+ end
8
+
9
+ it_should_behave_like 'a filesystem adapter'
10
+ it_should_behave_like 'an adapter that call properly listener#on_change'
11
+ end
12
+
13
+ if mac?
14
+ it "isn't usable on Mac OS X" do
15
+ described_class.should_not be_usable
16
+ end
17
+ end
18
+
19
+ if bsd?
20
+ it "isn't usable on BSD" do
21
+ described_class.should_not be_usable
22
+ end
23
+ end
24
+
25
+ if linux?
26
+ it "isn't usable on Linux" do
27
+ described_class.should_not be_usable
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,107 @@
1
+ require 'spec_helper'
2
+
3
+ describe Listen::DependencyManager do
4
+ let(:dependency) { Listen::DependencyManager::Dependency.new('listen', '~> 0.0.1') }
5
+
6
+ subject { Class.new { extend Listen::DependencyManager } }
7
+
8
+ before { described_class.clear_loaded }
9
+
10
+ describe '.add_loaded' do
11
+ it 'adds a dependency to the list of loaded dependencies' do
12
+ described_class.add_loaded dependency
13
+ described_class.already_loaded?(dependency).should be_true
14
+ end
15
+ end
16
+
17
+ describe '.already_loaded?' do
18
+ it 'returns false when a dependency is not in the list of loaded dependencies' do
19
+ described_class.already_loaded?(dependency).should be_false
20
+ end
21
+
22
+ it 'returns true when a dependency is in the list of loaded dependencies' do
23
+ described_class.add_loaded dependency
24
+ described_class.already_loaded?(dependency).should be_true
25
+ end
26
+ end
27
+
28
+ describe '.clear_loaded' do
29
+ it 'clears the whole list of loaded dependencies' do
30
+ described_class.add_loaded dependency
31
+ described_class.already_loaded?(dependency).should be_true
32
+ described_class.clear_loaded
33
+ described_class.already_loaded?(dependency).should be_false
34
+ end
35
+ end
36
+
37
+ describe '#dependency' do
38
+ it 'registers a new dependency for the managed class' do
39
+ subject.dependency 'listen', '~> 0.0.1'
40
+ subject.dependencies_loaded?.should be_false
41
+ end
42
+ end
43
+
44
+ describe '#load_depenencies' do
45
+ before { subject.dependency 'listen', '~> 0.0.1' }
46
+
47
+ context 'when dependencies can be loaded' do
48
+ before { subject.stub(:gem, :require) }
49
+
50
+ it 'loads all the registerd dependencies' do
51
+ subject.load_depenencies
52
+ subject.dependencies_loaded?.should be_true
53
+ end
54
+ end
55
+
56
+ context 'when dependencies can not be loaded' do
57
+ it 'raises an error' do
58
+ expect {
59
+ subject.load_depenencies
60
+ }.to raise_error(described_class::Error)
61
+ end
62
+
63
+ context 'when running under bundler' do
64
+ before { subject.should_receive(:running_under_bundler?).and_return(true) }
65
+
66
+ it 'includes the Gemfile declaration to satisfy the dependency' do
67
+ begin
68
+ subject.load_depenencies
69
+ rescue described_class::Error => e
70
+ e.message.should include("gem 'listen', '~> 0.0.1'")
71
+ end
72
+ end
73
+ end
74
+
75
+ context 'when not running under bundler' do
76
+ before { subject.should_receive(:running_under_bundler?).and_return(false) }
77
+
78
+ it 'includes the command to install the dependency' do
79
+ begin
80
+ subject.load_depenencies
81
+ rescue described_class::Error => e
82
+ e.message.should include("gem install --version '~> 0.0.1' listen")
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+
89
+ describe '#dependencies_loaded?' do
90
+ it 'return false when dependencies are not loaded' do
91
+ subject.dependency 'listen', '~> 0.0.1'
92
+ subject.dependencies_loaded?.should be_false
93
+ end
94
+
95
+ it 'return true when dependencies are loaded' do
96
+ subject.stub(:gem, :require)
97
+
98
+ subject.dependency 'listen', '~> 0.0.1'
99
+ subject.load_depenencies
100
+ subject.dependencies_loaded?.should be_true
101
+ end
102
+
103
+ it 'return true when there are no dependencies to load' do
104
+ subject.dependencies_loaded?.should be_true
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,1225 @@
1
+ # encoding: UTF-8
2
+ require 'spec_helper'
3
+
4
+ describe Listen::DirectoryRecord do
5
+ let(:base_directory) { File.dirname(__FILE__) }
6
+
7
+ subject { described_class.new(base_directory) }
8
+
9
+ describe '.generate_default_ignoring_patterns' do
10
+ it 'creates regexp patterns from the default ignored directories and extensions' do
11
+ described_class.generate_default_ignoring_patterns.should include(
12
+ %r{^(?:\.rbx|\.bundle|\.git|\.svn|log|tmp|vendor)/},
13
+ %r{(?:\.DS_Store)$}
14
+ )
15
+ end
16
+
17
+ it 'memoizes the generated results' do
18
+ described_class.generate_default_ignoring_patterns.should equal described_class.generate_default_ignoring_patterns
19
+ end
20
+ end
21
+
22
+ describe '#initialize' do
23
+ it 'sets the base directory' do
24
+ subject.directory.should eq base_directory
25
+ end
26
+
27
+ it 'sets the default ignoring patterns' do
28
+ subject.ignoring_patterns.should =~ described_class.generate_default_ignoring_patterns
29
+ end
30
+
31
+ it 'sets the default filtering patterns' do
32
+ subject.filtering_patterns.should eq []
33
+ end
34
+
35
+ it 'raises an error when the passed path does not exist' do
36
+ expect { described_class.new('no way I exist') }.to raise_error(ArgumentError)
37
+ end
38
+
39
+ it 'raises an error when the passed path is not a directory' do
40
+ expect { described_class.new(__FILE__) }.to raise_error(ArgumentError)
41
+ end
42
+ end
43
+
44
+ describe '#ignore' do
45
+ it 'adds the passed paths to the list of ignored paths in the record' do
46
+ subject.ignore(%r{^\.old/}, %r{\.pid$})
47
+ subject.ignoring_patterns.should include(%r{^\.old/}, %r{\.pid$})
48
+ end
49
+ end
50
+
51
+ describe '#ignore!' do
52
+ it 'replace the ignored paths in the record' do
53
+ subject.ignore!(%r{^\.old/}, %r{\.pid$})
54
+ subject.ignoring_patterns.should =~ [%r{^\.old/}, %r{\.pid$}]
55
+ end
56
+ end
57
+
58
+ describe '#filter' do
59
+ it 'adds the passed regexps to the list of filters that determine the stored paths' do
60
+ subject.filter(%r{\.(?:jpe?g|gif|png)}, %r{\.(?:mp3|ogg|a3c)})
61
+ subject.filtering_patterns.should include(%r{\.(?:jpe?g|gif|png)}, %r{\.(?:mp3|ogg|a3c)})
62
+ end
63
+ end
64
+
65
+ describe '#filter!' do
66
+ it 'replaces the passed regexps in the list of filters that determine the stored paths' do
67
+ subject.filter!(%r{\.(?:jpe?g|gif|png)}, %r{\.(?:mp3|ogg|a3c)})
68
+ subject.filtering_patterns.should =~ [%r{\.(?:mp3|ogg|a3c)}, %r{\.(?:jpe?g|gif|png)}]
69
+ end
70
+ end
71
+
72
+ describe '#ignored?' do
73
+ before { subject.stub(:relative_to_base) { |path| path } }
74
+
75
+ it 'tests paths relative to the base directory' do
76
+ subject.should_receive(:relative_to_base).with('file.txt')
77
+ subject.ignored?('file.txt')
78
+ end
79
+
80
+ it 'returns true when the passed path is a default ignored path' do
81
+ subject.ignored?('tmp/some_process.pid').should be_true
82
+ subject.ignored?('dir/.DS_Store').should be_true
83
+ subject.ignored?('.git/config').should be_true
84
+ end
85
+
86
+ it 'returns false when the passed path is not a default ignored path' do
87
+ subject.ignored?('nested/tmp/some_process.pid').should be_false
88
+ subject.ignored?('nested/.git').should be_false
89
+ subject.ignored?('dir/.DS_Store/file').should be_false
90
+ subject.ignored?('file.git').should be_false
91
+ end
92
+
93
+ it 'returns true when the passed path is ignored' do
94
+ subject.ignore(%r{\.pid$})
95
+ subject.ignored?('dir/some_process.pid').should be_true
96
+ end
97
+
98
+ it 'returns false when the passed path is not ignored' do
99
+ subject.ignore(%r{\.pid$})
100
+ subject.ignored?('dir/some_file.txt').should be_false
101
+ end
102
+ end
103
+
104
+ describe '#filtered?' do
105
+ before { subject.stub(:relative_to_base) { |path| path } }
106
+
107
+ context 'when no filtering patterns are set' do
108
+ it 'returns true for any path' do
109
+ subject.filtered?('file.txt').should be_true
110
+ end
111
+ end
112
+
113
+ context 'when filtering patterns are set' do
114
+ before { subject.filter(%r{\.(?:jpe?g|gif|png)}) }
115
+
116
+ it 'tests paths relative to the base directory' do
117
+ subject.should_receive(:relative_to_base).with('file.txt')
118
+ subject.filtered?('file.txt')
119
+ end
120
+
121
+ it 'returns true when the passed path is filtered' do
122
+ subject.filter(%r{\.(?:jpe?g|gif|png)})
123
+ subject.filtered?('dir/picture.jpeg').should be_true
124
+ end
125
+
126
+ it 'returns false when the passed path is not filtered' do
127
+ subject.filter(%r{\.(?:jpe?g|gif|png)})
128
+ subject.filtered?('dir/song.mp3').should be_false
129
+ end
130
+ end
131
+ end
132
+
133
+ describe '#build' do
134
+ it 'stores all files' do
135
+ fixtures do |path|
136
+ touch 'file.rb'
137
+ mkdir 'a_directory'
138
+ touch 'a_directory/file.txt'
139
+
140
+ record = described_class.new(path)
141
+ record.build
142
+
143
+ record.paths[path]['file.rb'].type.should eq 'File'
144
+ record.paths[path]['a_directory'].type.should eq 'Dir'
145
+ record.paths["#{path}/a_directory"]['file.txt'].type.should eq 'File'
146
+ end
147
+ end
148
+
149
+ context 'with ignored path set' do
150
+ it 'does not store ignored directory or its childs' do
151
+ fixtures do |path|
152
+ mkdir 'ignored_directory'
153
+ mkdir 'ignored_directory/child_directory'
154
+ touch 'ignored_directory/file.txt'
155
+
156
+ record = described_class.new(path)
157
+ record.ignore %r{^ignored_directory/}
158
+ record.build
159
+
160
+ record.paths[path]['/a_ignored_directory'].should be_nil
161
+ record.paths["#{path}/a_ignored_directory"]['child_directory'].should be_nil
162
+ record.paths["#{path}/a_ignored_directory"]['file.txt'].should be_nil
163
+ end
164
+ end
165
+
166
+ it 'does not store ignored files' do
167
+ fixtures do |path|
168
+ touch 'ignored_file.rb'
169
+
170
+ record = described_class.new(path)
171
+ record.ignore %r{^ignored_file.rb$}
172
+ record.build
173
+
174
+ record.paths[path]['ignored_file.rb'].should be_nil
175
+ end
176
+ end
177
+ end
178
+
179
+ context 'with filters set' do
180
+ it 'only stores filterd files' do
181
+ fixtures do |path|
182
+ touch 'file.rb'
183
+ touch 'file.zip'
184
+ mkdir 'a_directory'
185
+ touch 'a_directory/file.txt'
186
+ touch 'a_directory/file.rb'
187
+
188
+ record = described_class.new(path)
189
+ record.filter(/\.txt$/, /.*\.zip/)
190
+ record.build
191
+
192
+ record.paths[path]['file.rb'].should be_nil
193
+ record.paths[path]['file.zip'].type.should eq 'File'
194
+ record.paths[path]['a_directory'].type.should eq 'Dir'
195
+ record.paths["#{path}/a_directory"]['file.txt'].type.should eq 'File'
196
+ record.paths["#{path}/a_directory"]['file.rb'].should be_nil
197
+ end
198
+ end
199
+ end
200
+ end
201
+
202
+ describe '#relative_to_base' do
203
+ it 'removes the path of the base-directory from the passed path' do
204
+ path = 'dir/to/app/file.rb'
205
+ subject.relative_to_base(File.join(base_directory, path)).should eq path
206
+ end
207
+
208
+ it 'returns nil when the passed path is not inside the base-directory' do
209
+ subject.relative_to_base('/tmp/some_random_path').should be_nil
210
+ end
211
+
212
+ it 'works with non UTF-8 paths' do
213
+ path = "tmp/\xE4\xE4"
214
+ subject.relative_to_base(File.join(base_directory, path))
215
+ end
216
+
217
+ context 'when containing regexp characters in the base directory' do
218
+ before do
219
+ fixtures do |path|
220
+ mkdir 'a_directory$'
221
+ @dir = described_class.new(path + '/a_directory$')
222
+ @dir.build
223
+ end
224
+ end
225
+
226
+ it 'removes the path of the base-directory from the passed path' do
227
+ path = 'dir/to/app/file.rb'
228
+ @dir.relative_to_base(File.join(@dir.directory, path)).should eq path
229
+ end
230
+
231
+ it 'returns nil when the passed path is not inside the base-directory' do
232
+ @dir.relative_to_base('/tmp/some_random_path').should be_nil
233
+ end
234
+ end
235
+ end
236
+
237
+ describe '#fetch_changes' do
238
+ context 'with single file changes' do
239
+ context 'when a file is created' do
240
+ it 'detects the added file' do
241
+ fixtures do |path|
242
+ modified, added, removed = changes(path) do
243
+ touch 'new_file.rb'
244
+ end
245
+
246
+ added.should =~ %w(new_file.rb)
247
+ modified.should be_empty
248
+ removed.should be_empty
249
+ end
250
+ end
251
+
252
+ it 'stores the added file in the record' do
253
+ fixtures do |path|
254
+ changes(path) do
255
+ @record.paths.should be_empty
256
+
257
+ touch 'new_file.rb'
258
+ end
259
+
260
+ @record.paths[path]['new_file.rb'].should_not be_nil
261
+ end
262
+ end
263
+
264
+ context 'given a new created directory' do
265
+ it 'detects the added file' do
266
+ fixtures do |path|
267
+ modified, added, removed = changes(path) do
268
+ mkdir 'a_directory'
269
+ touch 'a_directory/new_file.rb'
270
+ end
271
+
272
+ added.should =~ %w(a_directory/new_file.rb)
273
+ modified.should be_empty
274
+ removed.should be_empty
275
+ end
276
+ end
277
+
278
+ it 'stores the added directory and file in the record' do
279
+ fixtures do |path|
280
+ changes(path) do
281
+ @record.paths.should be_empty
282
+
283
+ mkdir 'a_directory'
284
+ touch 'a_directory/new_file.rb'
285
+ end
286
+
287
+ @record.paths[path]['a_directory'].should_not be_nil
288
+ @record.paths["#{path}/a_directory"]['new_file.rb'].should_not be_nil
289
+ end
290
+ end
291
+ end
292
+
293
+ context 'given an existing directory' do
294
+ context 'with recursive option set to true' do
295
+ it 'detects the added file' do
296
+ fixtures do |path|
297
+ mkdir 'a_directory'
298
+
299
+ modified, added, removed = changes(path, :recursive => true) do
300
+ touch 'a_directory/new_file.rb'
301
+ end
302
+
303
+ added.should =~ %w(a_directory/new_file.rb)
304
+ modified.should be_empty
305
+ removed.should be_empty
306
+ end
307
+ end
308
+
309
+ context 'with an ignored directory' do
310
+ it "doesn't detect the added file" do
311
+ fixtures do |path|
312
+ mkdir 'ignored_directory'
313
+
314
+ modified, added, removed = changes(path, :ignore => %r{^ignored_directory/}, :recursive => true) do
315
+ touch 'ignored_directory/new_file.rb'
316
+ end
317
+
318
+ added.should be_empty
319
+ modified.should be_empty
320
+ removed.should be_empty
321
+ end
322
+ end
323
+
324
+ it "doesn't detect the added file when it's asked to fetch the changes of the ignored directory"do
325
+ fixtures do |path|
326
+ mkdir 'ignored_directory'
327
+
328
+ modified, added, removed = changes(path, :paths => ["#{path}/ignored_directory"], :ignore => %r{^ignored_directory/}, :recursive => true) do
329
+ touch 'ignored_directory/new_file.rb'
330
+ end
331
+
332
+ added.should be_empty
333
+ modified.should be_empty
334
+ removed.should be_empty
335
+ end
336
+ end
337
+ end
338
+ end
339
+
340
+ context 'with recursive option set to false' do
341
+ it "doesn't detect deeply-nested added files" do
342
+ fixtures do |path|
343
+ mkdir 'a_directory'
344
+
345
+ modified, added, removed = changes(path, :recursive => false) do
346
+ touch 'a_directory/new_file.rb'
347
+ end
348
+
349
+ added.should be_empty
350
+ modified.should be_empty
351
+ removed.should be_empty
352
+ end
353
+ end
354
+ end
355
+ end
356
+
357
+ context 'given a directory with subdirectories' do
358
+ it 'detects the added file' do
359
+ fixtures do |path|
360
+ mkdir_p 'a_directory/subdirectory'
361
+
362
+ modified, added, removed = changes(path, :recursive => true) do
363
+ touch 'a_directory/subdirectory/new_file.rb'
364
+ end
365
+
366
+ added.should =~ %w(a_directory/subdirectory/new_file.rb)
367
+ modified.should be_empty
368
+ removed.should be_empty
369
+ end
370
+ end
371
+
372
+ context 'with an ignored directory' do
373
+ it "doesn't detect added files in neither the directory nor the subdirectory" do
374
+ fixtures do |path|
375
+ mkdir_p 'ignored_directory/subdirectory'
376
+
377
+ modified, added, removed = changes(path, :ignore => %r{^ignored_directory/}, :recursive => true) do
378
+ touch 'ignored_directory/new_file.rb'
379
+ touch 'ignored_directory/subdirectory/new_file.rb'
380
+ end
381
+
382
+ added.should be_empty
383
+ modified.should be_empty
384
+ removed.should be_empty
385
+ end
386
+ end
387
+ end
388
+ end
389
+ end
390
+
391
+ context 'when a file is modified' do
392
+ it 'detects the modified file' do
393
+ fixtures do |path|
394
+ touch 'existing_file.txt'
395
+
396
+ modified, added, removed = changes(path) do
397
+ sleep 1.5 # make a difference in the mtime of the file
398
+ touch 'existing_file.txt'
399
+ end
400
+
401
+ added.should be_empty
402
+ modified.should =~ %w(existing_file.txt)
403
+ removed.should be_empty
404
+ end
405
+ end
406
+
407
+ context 'during the same second at which we are checking for changes' do
408
+ before { ensure_same_second }
409
+
410
+ # The following test can only be run on systems that report
411
+ # modification times in milliseconds.
412
+ it 'always detects the modified file the first time', :if => described_class::HIGH_PRECISION_SUPPORTED do
413
+ fixtures do |path|
414
+ touch 'existing_file.txt'
415
+
416
+ modified, added, removed = changes(path) do
417
+ sleep 0.3 # make sure the mtime is changed a bit
418
+ touch 'existing_file.txt'
419
+ end
420
+
421
+ added.should be_empty
422
+ modified.should =~ %w(existing_file.txt)
423
+ removed.should be_empty
424
+ end
425
+ end
426
+
427
+ context 'when a file is created and then checked for modifications at the same second - #27' do
428
+ # This issue was the result of checking a file for content changes when
429
+ # the mtime and the checking time are the same. In this case there
430
+ # is no checksum saved, so the file was reported as being changed.
431
+ it ' does not report any changes' do
432
+ fixtures do |path|
433
+ touch 'a_file.rb'
434
+
435
+ modified, added, removed = changes(path)
436
+
437
+ added.should be_empty
438
+ modified.should be_empty
439
+ removed.should be_empty
440
+ end
441
+ end
442
+ end
443
+
444
+ it "doesn't detects the modified file the second time if the content haven't changed" do
445
+ fixtures do |path|
446
+ touch 'existing_file.txt'
447
+
448
+ changes(path) do
449
+ touch 'existing_file.txt'
450
+ end
451
+
452
+ modified, added, removed = changes(path, :use_last_record => true) do
453
+ touch 'existing_file.txt'
454
+ end
455
+
456
+ added.should be_empty
457
+ modified.should be_empty
458
+ removed.should be_empty
459
+ end
460
+ end
461
+
462
+ it 'detects the modified file the second time if the content have changed' do
463
+ fixtures do |path|
464
+ touch 'existing_file.txt'
465
+ # Set sha1 path checksum
466
+ changes(path) do
467
+ touch 'existing_file.txt'
468
+ end
469
+ small_time_difference
470
+
471
+ changes(path) do
472
+ touch 'existing_file.txt'
473
+ end
474
+
475
+ modified, added, removed = changes(path, :use_last_record => true) do
476
+ open('existing_file.txt', 'w') { |f| f.write('foo') }
477
+ end
478
+
479
+ added.should be_empty
480
+ modified.should =~ %w(existing_file.txt)
481
+ removed.should be_empty
482
+ end
483
+ end
484
+
485
+ it "doesn't detects the modified file the second time if just touched - #62" do
486
+ fixtures do |path|
487
+ touch 'existing_file.txt'
488
+ # Set sha1 path checksum
489
+ changes(path) do
490
+ touch 'existing_file.txt'
491
+ end
492
+ small_time_difference
493
+
494
+ changes(path, :use_last_record => true) do
495
+ open('existing_file.txt', 'w') { |f| f.write('foo') }
496
+ end
497
+
498
+ modified, added, removed = changes(path, :use_last_record => true) do
499
+ touch 'existing_file.txt'
500
+ end
501
+
502
+ added.should be_empty
503
+ modified.should be_empty
504
+ removed.should be_empty
505
+ end
506
+ end
507
+
508
+ it "adds the path in the paths checksums if just touched - #62" do
509
+ fixtures do |path|
510
+ touch 'existing_file.txt'
511
+ small_time_difference
512
+
513
+ changes(path) do
514
+ touch 'existing_file.txt'
515
+ end
516
+
517
+ @record.sha1_checksums["#{path}/existing_file.txt"].should_not be_nil
518
+ end
519
+ end
520
+
521
+ it "deletes the path from the paths checksums" do
522
+ fixtures do |path|
523
+ touch 'unnecessary.txt'
524
+
525
+ changes(path) do
526
+ @record.sha1_checksums["#{path}/unnecessary.txt"] = 'foo'
527
+
528
+ rm 'unnecessary.txt'
529
+ end
530
+
531
+ @record.sha1_checksums["#{path}/unnecessary.txt"].should be_nil
532
+ end
533
+ end
534
+
535
+
536
+ end
537
+
538
+ context 'given a hidden file' do
539
+ it 'detects the modified file' do
540
+ fixtures do |path|
541
+ touch '.hidden'
542
+
543
+ modified, added, removed = changes(path) do
544
+ small_time_difference
545
+ touch '.hidden'
546
+ end
547
+
548
+ added.should be_empty
549
+ modified.should =~ %w(.hidden)
550
+ removed.should be_empty
551
+ end
552
+ end
553
+ end
554
+
555
+ context 'given a file mode change' do
556
+ it 'does not detect the mode change' do
557
+ fixtures do |path|
558
+ touch 'run.rb'
559
+ sleep 1.5 # make a difference in the mtime of the file
560
+
561
+ modified, added, removed = changes(path) do
562
+ chmod 0777, 'run.rb'
563
+ end
564
+
565
+ added.should be_empty
566
+ modified.should be_empty
567
+ removed.should be_empty
568
+ end
569
+ end
570
+ end
571
+
572
+ context 'given an existing directory' do
573
+ context 'with recursive option set to true' do
574
+ it 'detects the modified file' do
575
+ fixtures do |path|
576
+ mkdir 'a_directory'
577
+ touch 'a_directory/existing_file.txt'
578
+
579
+ modified, added, removed = changes(path, :recursive => true) do
580
+ small_time_difference
581
+ touch 'a_directory/existing_file.txt'
582
+ end
583
+
584
+ added.should be_empty
585
+ modified.should =~ %w(a_directory/existing_file.txt)
586
+ removed.should be_empty
587
+ end
588
+ end
589
+ end
590
+
591
+ context 'with recursive option set to false' do
592
+ it "doesn't detects the modified file" do
593
+ fixtures do |path|
594
+ mkdir 'a_directory'
595
+ touch 'a_directory/existing_file.txt'
596
+
597
+ modified, added, removed = changes(path, :recursive => false) do
598
+ small_time_difference
599
+ touch 'a_directory/existing_file.txt'
600
+ end
601
+
602
+ added.should be_empty
603
+ modified.should be_empty
604
+ removed.should be_empty
605
+ end
606
+ end
607
+ end
608
+ end
609
+
610
+ context 'given a directory with subdirectories' do
611
+ it 'detects the modified file' do
612
+ fixtures do |path|
613
+ mkdir_p 'a_directory/subdirectory'
614
+ touch 'a_directory/subdirectory/existing_file.txt'
615
+
616
+ modified, added, removed = changes(path, :recursive => true) do
617
+ small_time_difference
618
+ touch 'a_directory/subdirectory/existing_file.txt'
619
+ end
620
+
621
+ added.should be_empty
622
+ modified.should =~ %w(a_directory/subdirectory/existing_file.txt)
623
+ removed.should be_empty
624
+ end
625
+ end
626
+
627
+ context 'with an ignored subdirectory' do
628
+ it "doesn't detect the modified files in neither the directory nor the subdirectory" do
629
+ fixtures do |path|
630
+ mkdir_p 'ignored_directory/subdirectory'
631
+ touch 'ignored_directory/existing_file.txt'
632
+ touch 'ignored_directory/subdirectory/existing_file.txt'
633
+
634
+ modified, added, removed = changes(path, :ignore => %r{^ignored_directory/}, :recursive => true) do
635
+ touch 'ignored_directory/existing_file.txt'
636
+ touch 'ignored_directory/subdirectory/existing_file.txt'
637
+ end
638
+
639
+ added.should be_empty
640
+ modified.should be_empty
641
+ removed.should be_empty
642
+ end
643
+ end
644
+ end
645
+ end
646
+ end
647
+
648
+ context 'when a file is moved' do
649
+ it 'detects the file movement' do
650
+ fixtures do |path|
651
+ touch 'move_me.txt'
652
+
653
+ modified, added, removed = changes(path) do
654
+ mv 'move_me.txt', 'new_name.txt'
655
+ end
656
+
657
+ added.should =~ %w(new_name.txt)
658
+ modified.should be_empty
659
+ removed.should =~ %w(move_me.txt)
660
+ end
661
+ end
662
+
663
+ context 'given an existing directory' do
664
+ context 'with recursive option set to true' do
665
+ it 'detects the file movement into the directory' do
666
+ fixtures do |path|
667
+ mkdir 'a_directory'
668
+ touch 'move_me.txt'
669
+
670
+ modified, added, removed = changes(path, :recursive => true) do
671
+ mv 'move_me.txt', 'a_directory/move_me.txt'
672
+ end
673
+
674
+ added.should =~ %w(a_directory/move_me.txt)
675
+ modified.should be_empty
676
+ removed.should =~ %w(move_me.txt)
677
+ end
678
+ end
679
+
680
+ it 'detects a file movement out of the directory' do
681
+ fixtures do |path|
682
+ mkdir 'a_directory'
683
+ touch 'a_directory/move_me.txt'
684
+
685
+ modified, added, removed = changes(path, :recursive => true) do
686
+ mv 'a_directory/move_me.txt', 'i_am_here.txt'
687
+ end
688
+
689
+ added.should =~ %w(i_am_here.txt)
690
+ modified.should be_empty
691
+ removed.should =~ %w(a_directory/move_me.txt)
692
+ end
693
+ end
694
+
695
+ it 'detects a file movement between two directories' do
696
+ fixtures do |path|
697
+ mkdir 'from_directory'
698
+ touch 'from_directory/move_me.txt'
699
+ mkdir 'to_directory'
700
+
701
+ modified, added, removed = changes(path, :recursive => true) do
702
+ mv 'from_directory/move_me.txt', 'to_directory/move_me.txt'
703
+ end
704
+
705
+ added.should =~ %w(to_directory/move_me.txt)
706
+ modified.should be_empty
707
+ removed.should =~ %w(from_directory/move_me.txt)
708
+ end
709
+ end
710
+ end
711
+
712
+ context 'with recursive option set to false' do
713
+ it "doesn't detect the file movement into the directory" do
714
+ fixtures do |path|
715
+ mkdir 'a_directory'
716
+ touch 'move_me.txt'
717
+
718
+ modified, added, removed = changes(path, :recursive => false) do
719
+ mv 'move_me.txt', 'a_directory/move_me.txt'
720
+ end
721
+
722
+ added.should be_empty
723
+ modified.should be_empty
724
+ removed.should =~ %w(move_me.txt)
725
+ end
726
+ end
727
+
728
+ it "doesn't detect a file movement out of the directory" do
729
+ fixtures do |path|
730
+ mkdir 'a_directory'
731
+ touch 'a_directory/move_me.txt'
732
+
733
+ modified, added, removed = changes(path, :recursive => false) do
734
+ mv 'a_directory/move_me.txt', 'i_am_here.txt'
735
+ end
736
+
737
+ added.should =~ %w(i_am_here.txt)
738
+ modified.should be_empty
739
+ removed.should be_empty
740
+ end
741
+ end
742
+
743
+ it "doesn't detect a file movement between two directories" do
744
+ fixtures do |path|
745
+ mkdir 'from_directory'
746
+ touch 'from_directory/move_me.txt'
747
+ mkdir 'to_directory'
748
+
749
+ modified, added, removed = changes(path, :recursive => false) do
750
+ mv 'from_directory/move_me.txt', 'to_directory/move_me.txt'
751
+ end
752
+
753
+ added.should be_empty
754
+ modified.should be_empty
755
+ removed.should be_empty
756
+ end
757
+ end
758
+
759
+ context 'given a directory with subdirectories' do
760
+ it 'detects a file movement between two subdirectories' do
761
+ fixtures do |path|
762
+ mkdir_p 'a_directory/subdirectory'
763
+ mkdir_p 'b_directory/subdirectory'
764
+ touch 'a_directory/subdirectory/move_me.txt'
765
+
766
+ modified, added, removed = changes(path, :recursive => true) do
767
+ mv 'a_directory/subdirectory/move_me.txt', 'b_directory/subdirectory'
768
+ end
769
+
770
+ added.should =~ %w(b_directory/subdirectory/move_me.txt)
771
+ modified.should be_empty
772
+ removed.should =~ %w(a_directory/subdirectory/move_me.txt)
773
+ end
774
+ end
775
+
776
+ context 'with an ignored subdirectory' do
777
+ it "doesn't detect the file movement between subdirectories" do
778
+ fixtures do |path|
779
+ mkdir_p 'a_ignored_directory/subdirectory'
780
+ mkdir_p 'b_ignored_directory/subdirectory'
781
+ touch 'a_ignored_directory/subdirectory/move_me.txt'
782
+
783
+ modified, added, removed = changes(path, :ignore => %r{^(?:a|b)_ignored_directory/}, :recursive => true) do
784
+ mv 'a_ignored_directory/subdirectory/move_me.txt', 'b_ignored_directory/subdirectory'
785
+ end
786
+
787
+ added.should be_empty
788
+ modified.should be_empty
789
+ removed.should be_empty
790
+ end
791
+ end
792
+ end
793
+ end
794
+
795
+ context 'with all paths passed as params' do
796
+ it 'detects the file movement into the directory' do
797
+ fixtures do |path|
798
+ mkdir 'a_directory'
799
+ touch 'move_me.txt'
800
+
801
+ modified, added, removed = changes(path, :recursive => false, :paths => [path, "#{path}/a_directory"]) do
802
+ mv 'move_me.txt', 'a_directory/move_me.txt'
803
+ end
804
+
805
+ added.should =~ %w(a_directory/move_me.txt)
806
+ modified.should be_empty
807
+ removed.should =~ %w(move_me.txt)
808
+ end
809
+ end
810
+
811
+ it 'detects a file moved outside of a directory' do
812
+ fixtures do |path|
813
+ mkdir 'a_directory'
814
+ touch 'a_directory/move_me.txt'
815
+
816
+ modified, added, removed = changes(path, :recursive => false, :paths => [path, "#{path}/a_directory"]) do
817
+ mv 'a_directory/move_me.txt', 'i_am_here.txt'
818
+ end
819
+
820
+ added.should =~ %w(i_am_here.txt)
821
+ modified.should be_empty
822
+ removed.should =~ %w(a_directory/move_me.txt)
823
+ end
824
+ end
825
+
826
+ it 'detects a file movement between two directories' do
827
+ fixtures do |path|
828
+ mkdir 'from_directory'
829
+ touch 'from_directory/move_me.txt'
830
+ mkdir 'to_directory'
831
+
832
+ modified, added, removed = changes(path, :recursive => false, :paths => [path, "#{path}/from_directory", "#{path}/to_directory"]) do
833
+ mv 'from_directory/move_me.txt', 'to_directory/move_me.txt'
834
+ end
835
+
836
+ added.should =~ %w(to_directory/move_me.txt)
837
+ modified.should be_empty
838
+ removed.should =~ %w(from_directory/move_me.txt)
839
+ end
840
+ end
841
+ end
842
+ end
843
+ end
844
+ end
845
+
846
+ context 'when a file is deleted' do
847
+ it 'detects the file removal' do
848
+ fixtures do |path|
849
+ touch 'unnecessary.txt'
850
+
851
+ modified, added, removed = changes(path) do
852
+ rm 'unnecessary.txt'
853
+ end
854
+
855
+ added.should be_empty
856
+ modified.should be_empty
857
+ removed.should =~ %w(unnecessary.txt)
858
+ end
859
+ end
860
+
861
+ it "deletes the file from the record" do
862
+ fixtures do |path|
863
+ touch 'unnecessary.txt'
864
+
865
+ changes(path) do
866
+ @record.paths[path]['unnecessary.txt'].should_not be_nil
867
+
868
+ rm 'unnecessary.txt'
869
+ end
870
+
871
+ @record.paths[path]['unnecessary.txt'].should be_nil
872
+ end
873
+ end
874
+
875
+ it "deletes the path from the paths checksums" do
876
+ fixtures do |path|
877
+ touch 'unnecessary.txt'
878
+
879
+ changes(path) do
880
+ @record.sha1_checksums["#{path}/unnecessary.txt"] = 'foo'
881
+
882
+ rm 'unnecessary.txt'
883
+ end
884
+
885
+ @record.sha1_checksums["#{path}/unnecessary.txt"].should be_nil
886
+ end
887
+ end
888
+
889
+ context 'given an existing directory' do
890
+ context 'with recursive option set to true' do
891
+ it 'detects the file removal' do
892
+ fixtures do |path|
893
+ mkdir 'a_directory'
894
+ touch 'a_directory/do_not_use.rb'
895
+
896
+ modified, added, removed = changes(path, :recursive => true) do
897
+ rm 'a_directory/do_not_use.rb'
898
+ end
899
+
900
+ added.should be_empty
901
+ modified.should be_empty
902
+ removed.should =~ %w(a_directory/do_not_use.rb)
903
+ end
904
+ end
905
+ end
906
+
907
+ context 'with recursive option set to false' do
908
+ it "doesn't detect the file removal" do
909
+ fixtures do |path|
910
+ mkdir 'a_directory'
911
+ touch 'a_directory/do_not_use.rb'
912
+
913
+ modified, added, removed = changes(path, :recursive => false) do
914
+ rm 'a_directory/do_not_use.rb'
915
+ end
916
+
917
+ added.should be_empty
918
+ modified.should be_empty
919
+ removed.should be_empty
920
+ end
921
+ end
922
+ end
923
+ end
924
+
925
+ context 'given a directory with subdirectories' do
926
+ it 'detects the file removal in subdirectories' do
927
+ fixtures do |path|
928
+ mkdir_p 'a_directory/subdirectory'
929
+ touch 'a_directory/subdirectory/do_not_use.rb'
930
+
931
+ modified, added, removed = changes(path, :recursive => true) do
932
+ rm 'a_directory/subdirectory/do_not_use.rb'
933
+ end
934
+
935
+ added.should be_empty
936
+ modified.should be_empty
937
+ removed.should =~ %w(a_directory/subdirectory/do_not_use.rb)
938
+ end
939
+ end
940
+
941
+ context 'with an ignored subdirectory' do
942
+ it "doesn't detect files removals in neither the directory nor its subdirectories" do
943
+ fixtures do |path|
944
+ mkdir_p 'ignored_directory/subdirectory'
945
+ touch 'ignored_directory/do_not_use.rb'
946
+ touch 'ignored_directory/subdirectory/do_not_use.rb'
947
+
948
+ modified, added, removed = changes(path, :ignore => %r{^ignored_directory/}, :recursive => true) do
949
+ rm 'ignored_directory/do_not_use.rb'
950
+ rm 'ignored_directory/subdirectory/do_not_use.rb'
951
+ end
952
+
953
+ added.should be_empty
954
+ modified.should be_empty
955
+ removed.should be_empty
956
+ end
957
+ end
958
+ end
959
+ end
960
+ end
961
+ end
962
+
963
+ context 'multiple file operations' do
964
+ it 'detects the added files' do
965
+ fixtures do |path|
966
+ modified, added, removed = changes(path) do
967
+ touch 'a_file.rb'
968
+ touch 'b_file.rb'
969
+ mkdir 'a_directory'
970
+ touch 'a_directory/a_file.rb'
971
+ touch 'a_directory/b_file.rb'
972
+ end
973
+
974
+ added.should =~ %w(a_file.rb b_file.rb a_directory/a_file.rb a_directory/b_file.rb)
975
+ modified.should be_empty
976
+ removed.should be_empty
977
+ end
978
+ end
979
+
980
+ it 'detects the modified files' do
981
+ fixtures do |path|
982
+ touch 'a_file.rb'
983
+ touch 'b_file.rb'
984
+ mkdir 'a_directory'
985
+ touch 'a_directory/a_file.rb'
986
+ touch 'a_directory/b_file.rb'
987
+
988
+ small_time_difference
989
+
990
+ modified, added, removed = changes(path) do
991
+ touch 'b_file.rb'
992
+ touch 'a_directory/a_file.rb'
993
+ end
994
+
995
+ added.should be_empty
996
+ modified.should =~ %w(b_file.rb a_directory/a_file.rb)
997
+ removed.should be_empty
998
+ end
999
+ end
1000
+
1001
+ it 'detects the removed files' do
1002
+ fixtures do |path|
1003
+ touch 'a_file.rb'
1004
+ touch 'b_file.rb'
1005
+ mkdir 'a_directory'
1006
+ touch 'a_directory/a_file.rb'
1007
+ touch 'a_directory/b_file.rb'
1008
+
1009
+ modified, added, removed = changes(path) do
1010
+ rm 'b_file.rb'
1011
+ rm 'a_directory/a_file.rb'
1012
+ end
1013
+
1014
+ added.should be_empty
1015
+ modified.should be_empty
1016
+ removed.should =~ %w(b_file.rb a_directory/a_file.rb)
1017
+ end
1018
+ end
1019
+ end
1020
+
1021
+ context 'single directory operations' do
1022
+ it 'detects a moved directory' do
1023
+ fixtures do |path|
1024
+ mkdir 'a_directory'
1025
+ touch 'a_directory/a_file.rb'
1026
+ touch 'a_directory/b_file.rb'
1027
+
1028
+ modified, added, removed = changes(path) do
1029
+ mv 'a_directory', 'renamed'
1030
+ end
1031
+
1032
+ added.should =~ %w(renamed/a_file.rb renamed/b_file.rb)
1033
+ modified.should be_empty
1034
+ removed.should =~ %w(a_directory/a_file.rb a_directory/b_file.rb)
1035
+ end
1036
+ end
1037
+
1038
+ it 'detects a removed directory' do
1039
+ fixtures do |path|
1040
+ mkdir 'a_directory'
1041
+ touch 'a_directory/a_file.rb'
1042
+ touch 'a_directory/b_file.rb'
1043
+
1044
+ modified, added, removed = changes(path) do
1045
+ rm_rf 'a_directory'
1046
+ end
1047
+
1048
+ added.should be_empty
1049
+ modified.should be_empty
1050
+ removed.should =~ %w(a_directory/a_file.rb a_directory/b_file.rb)
1051
+ end
1052
+ end
1053
+
1054
+ it "deletes the directory from the record" do
1055
+ fixtures do |path|
1056
+ mkdir 'a_directory'
1057
+ touch 'a_directory/file.rb'
1058
+
1059
+ changes(path) do
1060
+ @record.paths.should have(2).paths
1061
+ @record.paths[path]['a_directory'].should_not be_nil
1062
+ @record.paths["#{path}/a_directory"]['file.rb'].should_not be_nil
1063
+
1064
+ rm_rf 'a_directory'
1065
+ end
1066
+
1067
+ @record.paths.should have(1).paths
1068
+ @record.paths[path]['a_directory'].should be_nil
1069
+ @record.paths["#{path}/a_directory"]['file.rb'].should be_nil
1070
+ end
1071
+ end
1072
+
1073
+ context 'with nested paths' do
1074
+ it 'detects removals without crashing - #18' do
1075
+ fixtures do |path|
1076
+ mkdir_p 'a_directory/subdirectory'
1077
+ touch 'a_directory/subdirectory/do_not_use.rb'
1078
+
1079
+ modified, added, removed = changes(path) do
1080
+ rm_r 'a_directory'
1081
+ end
1082
+
1083
+ added.should be_empty
1084
+ modified.should be_empty
1085
+ removed.should =~ %w(a_directory/subdirectory/do_not_use.rb)
1086
+ end
1087
+ end
1088
+ end
1089
+ end
1090
+
1091
+ context 'with a path outside the directory for which a record is made' do
1092
+ it "skips that path and doesn't check for changes" do
1093
+ fixtures do |path|
1094
+ modified, added, removed = changes(path, :paths => ['some/where/outside']) do
1095
+ @record.should_not_receive(:detect_additions)
1096
+ @record.should_not_receive(:detect_modifications_and_removals)
1097
+
1098
+ touch 'new_file.rb'
1099
+ end
1100
+
1101
+ added.should be_empty
1102
+ modified.should be_empty
1103
+ removed.should be_empty
1104
+ end
1105
+ end
1106
+ end
1107
+
1108
+ context 'with the relative_paths option set to false' do
1109
+ it 'returns full paths in the changes hash' do
1110
+ fixtures do |path|
1111
+ touch 'a_file.rb'
1112
+ touch 'b_file.rb'
1113
+
1114
+ modified, added, removed = changes(path, :relative_paths => false) do
1115
+ small_time_difference
1116
+ rm 'a_file.rb'
1117
+ touch 'b_file.rb'
1118
+ touch 'c_file.rb'
1119
+ mkdir 'a_directory'
1120
+ touch 'a_directory/a_file.rb'
1121
+ end
1122
+
1123
+ added.should =~ ["#{path}/c_file.rb", "#{path}/a_directory/a_file.rb"]
1124
+ modified.should =~ ["#{path}/b_file.rb"]
1125
+ removed.should =~ ["#{path}/a_file.rb"]
1126
+ end
1127
+ end
1128
+ end
1129
+
1130
+ context 'within a directory containing unreadble paths - #32' do
1131
+ it 'detects changes more than a second apart' do
1132
+ fixtures do |path|
1133
+ touch 'unreadable_file.txt'
1134
+ chmod 000, 'unreadable_file.txt'
1135
+
1136
+ modified, added, removed = changes(path) do
1137
+ sleep 1.1
1138
+ touch 'unreadable_file.txt'
1139
+ end
1140
+
1141
+ added.should be_empty
1142
+ modified.should =~ %w(unreadable_file.txt)
1143
+ removed.should be_empty
1144
+ end
1145
+ end
1146
+
1147
+ context 'with multiple changes within the same second' do
1148
+ before { ensure_same_second }
1149
+
1150
+ it 'does not detect changes even if content changes', :unless => described_class::HIGH_PRECISION_SUPPORTED do
1151
+ fixtures do |path|
1152
+ touch 'unreadable_file.txt'
1153
+
1154
+ modified, added, removed = changes(path) do
1155
+ open('unreadable_file.txt', 'w') { |f| f.write('foo') }
1156
+ chmod 000, 'unreadable_file.txt'
1157
+ end
1158
+
1159
+ added.should be_empty
1160
+ modified.should be_empty
1161
+ removed.should be_empty
1162
+ end
1163
+ end
1164
+ end
1165
+ end
1166
+
1167
+ context 'within a directory containing a removed file - #39' do
1168
+ it 'does not raise an exception when hashing a removed file' do
1169
+
1170
+ # simulate a race condition where the file is removed after the
1171
+ # change event is tracked, but before the hash is calculated
1172
+ Digest::SHA1.should_receive(:file).twice.and_raise(Errno::ENOENT)
1173
+
1174
+ lambda {
1175
+ fixtures do |path|
1176
+ file = 'removed_file.txt'
1177
+ touch file
1178
+ changes(path) { touch file }
1179
+ end
1180
+ }.should_not raise_error(Errno::ENOENT)
1181
+ end
1182
+ end
1183
+
1184
+ context 'within a directory containing a unix domain socket file' do
1185
+ it 'does not raise an exception when hashing a unix domain socket file' do
1186
+ fixtures do |path|
1187
+ require 'socket'
1188
+ UNIXServer.new('unix_domain_socket.sock')
1189
+ lambda { changes(path){} }.should_not raise_error(Errno::ENXIO)
1190
+ end
1191
+ end
1192
+ end
1193
+
1194
+ context 'with symlinks', :unless => windows? do
1195
+ it 'looks at symlinks not their targets' do
1196
+ fixtures do |path|
1197
+ touch 'target'
1198
+ symlink 'target', 'symlink'
1199
+
1200
+ record = described_class.new(path)
1201
+ record.build
1202
+
1203
+ sleep 1
1204
+ touch 'target'
1205
+
1206
+ record.fetch_changes([path], :relative_paths => true)[:modified].should == ['target']
1207
+ end
1208
+ end
1209
+
1210
+ it 'handles broken symlinks' do
1211
+ fixtures do |path|
1212
+ symlink 'target', 'symlink'
1213
+
1214
+ record = described_class.new(path)
1215
+ record.build
1216
+
1217
+ sleep 1
1218
+ rm 'symlink'
1219
+ symlink 'new-target', 'symlink'
1220
+ record.fetch_changes([path], :relative_paths => true)
1221
+ end
1222
+ end
1223
+ end
1224
+ end
1225
+ end