sass 3.7.4 → 4.0.0.alpha.1

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