sass 3.1.0 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (260) hide show
  1. checksums.yaml +7 -0
  2. data/CONTRIBUTING +1 -1
  3. data/MIT-LICENSE +2 -2
  4. data/README.md +29 -17
  5. data/Rakefile +43 -9
  6. data/VERSION +1 -1
  7. data/VERSION_DATE +1 -0
  8. data/VERSION_NAME +1 -1
  9. data/bin/sass +6 -1
  10. data/bin/sass-convert +6 -1
  11. data/bin/scss +6 -1
  12. data/ext/mkrf_conf.rb +27 -0
  13. data/lib/sass/cache_stores/base.rb +7 -3
  14. data/lib/sass/cache_stores/chain.rb +3 -2
  15. data/lib/sass/cache_stores/filesystem.rb +5 -7
  16. data/lib/sass/cache_stores/memory.rb +1 -1
  17. data/lib/sass/cache_stores/null.rb +2 -2
  18. data/lib/sass/callbacks.rb +2 -1
  19. data/lib/sass/css.rb +168 -53
  20. data/lib/sass/engine.rb +502 -174
  21. data/lib/sass/environment.rb +151 -111
  22. data/lib/sass/error.rb +7 -7
  23. data/lib/sass/exec.rb +176 -60
  24. data/lib/sass/features.rb +40 -0
  25. data/lib/sass/importers/base.rb +46 -7
  26. data/lib/sass/importers/deprecated_path.rb +51 -0
  27. data/lib/sass/importers/filesystem.rb +113 -30
  28. data/lib/sass/importers.rb +1 -0
  29. data/lib/sass/logger/base.rb +30 -0
  30. data/lib/sass/logger/log_level.rb +45 -0
  31. data/lib/sass/logger.rb +12 -0
  32. data/lib/sass/media.rb +213 -0
  33. data/lib/sass/plugin/compiler.rb +194 -104
  34. data/lib/sass/plugin/configuration.rb +18 -25
  35. data/lib/sass/plugin/merb.rb +1 -1
  36. data/lib/sass/plugin/staleness_checker.rb +37 -11
  37. data/lib/sass/plugin.rb +10 -13
  38. data/lib/sass/railtie.rb +2 -1
  39. data/lib/sass/repl.rb +5 -6
  40. data/lib/sass/script/css_lexer.rb +8 -4
  41. data/lib/sass/script/css_parser.rb +5 -2
  42. data/lib/sass/script/functions.rb +1547 -618
  43. data/lib/sass/script/lexer.rb +122 -72
  44. data/lib/sass/script/parser.rb +304 -135
  45. data/lib/sass/script/tree/funcall.rb +306 -0
  46. data/lib/sass/script/{interpolation.rb → tree/interpolation.rb} +43 -13
  47. data/lib/sass/script/tree/list_literal.rb +77 -0
  48. data/lib/sass/script/tree/literal.rb +45 -0
  49. data/lib/sass/script/tree/map_literal.rb +64 -0
  50. data/lib/sass/script/{node.rb → tree/node.rb} +30 -12
  51. data/lib/sass/script/{operation.rb → tree/operation.rb} +33 -21
  52. data/lib/sass/script/{string_interpolation.rb → tree/string_interpolation.rb} +14 -4
  53. data/lib/sass/script/{unary_operation.rb → tree/unary_operation.rb} +21 -9
  54. data/lib/sass/script/tree/variable.rb +57 -0
  55. data/lib/sass/script/tree.rb +15 -0
  56. data/lib/sass/script/value/arg_list.rb +36 -0
  57. data/lib/sass/script/value/base.rb +238 -0
  58. data/lib/sass/script/value/bool.rb +40 -0
  59. data/lib/sass/script/{color.rb → value/color.rb} +256 -74
  60. data/lib/sass/script/value/deprecated_false.rb +55 -0
  61. data/lib/sass/script/value/helpers.rb +155 -0
  62. data/lib/sass/script/value/list.rb +128 -0
  63. data/lib/sass/script/value/map.rb +70 -0
  64. data/lib/sass/script/value/null.rb +49 -0
  65. data/lib/sass/script/{number.rb → value/number.rb} +115 -62
  66. data/lib/sass/script/{string.rb → value/string.rb} +9 -11
  67. data/lib/sass/script/value.rb +12 -0
  68. data/lib/sass/script.rb +35 -9
  69. data/lib/sass/scss/css_parser.rb +2 -12
  70. data/lib/sass/scss/parser.rb +657 -230
  71. data/lib/sass/scss/rx.rb +17 -12
  72. data/lib/sass/scss/static_parser.rb +37 -6
  73. data/lib/sass/scss.rb +0 -1
  74. data/lib/sass/selector/abstract_sequence.rb +35 -3
  75. data/lib/sass/selector/comma_sequence.rb +29 -14
  76. data/lib/sass/selector/sequence.rb +371 -74
  77. data/lib/sass/selector/simple.rb +28 -13
  78. data/lib/sass/selector/simple_sequence.rb +163 -36
  79. data/lib/sass/selector.rb +138 -36
  80. data/lib/sass/shared.rb +3 -5
  81. data/lib/sass/source/map.rb +196 -0
  82. data/lib/sass/source/position.rb +39 -0
  83. data/lib/sass/source/range.rb +41 -0
  84. data/lib/sass/stack.rb +126 -0
  85. data/lib/sass/supports.rb +228 -0
  86. data/lib/sass/tree/at_root_node.rb +82 -0
  87. data/lib/sass/tree/comment_node.rb +34 -29
  88. data/lib/sass/tree/content_node.rb +9 -0
  89. data/lib/sass/tree/css_import_node.rb +60 -0
  90. data/lib/sass/tree/debug_node.rb +3 -3
  91. data/lib/sass/tree/directive_node.rb +33 -3
  92. data/lib/sass/tree/each_node.rb +9 -9
  93. data/lib/sass/tree/extend_node.rb +20 -6
  94. data/lib/sass/tree/for_node.rb +6 -6
  95. data/lib/sass/tree/function_node.rb +12 -4
  96. data/lib/sass/tree/if_node.rb +2 -15
  97. data/lib/sass/tree/import_node.rb +11 -5
  98. data/lib/sass/tree/media_node.rb +27 -11
  99. data/lib/sass/tree/mixin_def_node.rb +15 -4
  100. data/lib/sass/tree/mixin_node.rb +27 -7
  101. data/lib/sass/tree/node.rb +69 -35
  102. data/lib/sass/tree/prop_node.rb +47 -31
  103. data/lib/sass/tree/return_node.rb +4 -3
  104. data/lib/sass/tree/root_node.rb +20 -4
  105. data/lib/sass/tree/rule_node.rb +37 -26
  106. data/lib/sass/tree/supports_node.rb +38 -0
  107. data/lib/sass/tree/trace_node.rb +33 -0
  108. data/lib/sass/tree/variable_node.rb +10 -4
  109. data/lib/sass/tree/visitors/base.rb +5 -8
  110. data/lib/sass/tree/visitors/check_nesting.rb +67 -52
  111. data/lib/sass/tree/visitors/convert.rb +134 -53
  112. data/lib/sass/tree/visitors/cssize.rb +245 -51
  113. data/lib/sass/tree/visitors/deep_copy.rb +102 -0
  114. data/lib/sass/tree/visitors/extend.rb +68 -0
  115. data/lib/sass/tree/visitors/perform.rb +331 -105
  116. data/lib/sass/tree/visitors/set_options.rb +125 -0
  117. data/lib/sass/tree/visitors/to_css.rb +259 -95
  118. data/lib/sass/tree/warn_node.rb +3 -3
  119. data/lib/sass/tree/while_node.rb +3 -3
  120. data/lib/sass/util/cross_platform_random.rb +19 -0
  121. data/lib/sass/util/multibyte_string_scanner.rb +157 -0
  122. data/lib/sass/util/normalized_map.rb +130 -0
  123. data/lib/sass/util/ordered_hash.rb +192 -0
  124. data/lib/sass/util/subset_map.rb +11 -2
  125. data/lib/sass/util/test.rb +9 -0
  126. data/lib/sass/util.rb +565 -39
  127. data/lib/sass/version.rb +27 -15
  128. data/lib/sass.rb +39 -4
  129. data/test/sass/cache_test.rb +15 -0
  130. data/test/sass/compiler_test.rb +223 -0
  131. data/test/sass/conversion_test.rb +901 -107
  132. data/test/sass/css2sass_test.rb +94 -0
  133. data/test/sass/engine_test.rb +1059 -164
  134. data/test/sass/exec_test.rb +86 -0
  135. data/test/sass/extend_test.rb +933 -837
  136. data/test/sass/fixtures/test_staleness_check_across_importers.css +1 -0
  137. data/test/sass/fixtures/test_staleness_check_across_importers.scss +1 -0
  138. data/test/sass/functions_test.rb +995 -136
  139. data/test/sass/importer_test.rb +338 -18
  140. data/test/sass/logger_test.rb +58 -0
  141. data/test/sass/more_results/more_import.css +2 -2
  142. data/test/sass/plugin_test.rb +114 -30
  143. data/test/sass/results/cached_import_option.css +3 -0
  144. data/test/sass/results/filename_fn.css +3 -0
  145. data/test/sass/results/import.css +2 -2
  146. data/test/sass/results/import_charset.css +1 -0
  147. data/test/sass/results/import_charset_1_8.css +1 -0
  148. data/test/sass/results/import_charset_ibm866.css +1 -0
  149. data/test/sass/results/import_content.css +1 -0
  150. data/test/sass/results/script.css +1 -1
  151. data/test/sass/results/scss_import.css +2 -2
  152. data/test/sass/results/units.css +2 -2
  153. data/test/sass/script_conversion_test.rb +43 -1
  154. data/test/sass/script_test.rb +380 -36
  155. data/test/sass/scss/css_test.rb +257 -75
  156. data/test/sass/scss/scss_test.rb +2322 -110
  157. data/test/sass/source_map_test.rb +887 -0
  158. data/test/sass/templates/_cached_import_option_partial.scss +1 -0
  159. data/test/sass/templates/_double_import_loop2.sass +1 -0
  160. data/test/sass/templates/_filename_fn_import.scss +11 -0
  161. data/test/sass/templates/_imported_content.sass +3 -0
  162. data/test/sass/templates/_same_name_different_partiality.scss +1 -0
  163. data/test/sass/templates/bork5.sass +3 -0
  164. data/test/sass/templates/cached_import_option.scss +3 -0
  165. data/test/sass/templates/double_import_loop1.sass +1 -0
  166. data/test/sass/templates/filename_fn.scss +18 -0
  167. data/test/sass/templates/import_charset.sass +2 -0
  168. data/test/sass/templates/import_charset_1_8.sass +2 -0
  169. data/test/sass/templates/import_charset_ibm866.sass +2 -0
  170. data/test/sass/templates/import_content.sass +4 -0
  171. data/test/sass/templates/same_name_different_ext.sass +2 -0
  172. data/test/sass/templates/same_name_different_ext.scss +1 -0
  173. data/test/sass/templates/same_name_different_partiality.scss +1 -0
  174. data/test/sass/templates/single_import_loop.sass +1 -0
  175. data/test/sass/templates/subdir/import_up1.scss +1 -0
  176. data/test/sass/templates/subdir/import_up2.scss +1 -0
  177. data/test/sass/test_helper.rb +1 -1
  178. data/test/sass/util/multibyte_string_scanner_test.rb +147 -0
  179. data/test/sass/util/normalized_map_test.rb +51 -0
  180. data/test/sass/util_test.rb +183 -0
  181. data/test/sass/value_helpers_test.rb +181 -0
  182. data/test/test_helper.rb +45 -5
  183. data/vendor/listen/CHANGELOG.md +228 -0
  184. data/vendor/listen/CONTRIBUTING.md +38 -0
  185. data/vendor/listen/Gemfile +30 -0
  186. data/vendor/listen/Guardfile +8 -0
  187. data/vendor/{fssm → listen}/LICENSE +1 -1
  188. data/vendor/listen/README.md +315 -0
  189. data/vendor/listen/Rakefile +47 -0
  190. data/vendor/listen/Vagrantfile +96 -0
  191. data/vendor/listen/lib/listen/adapter.rb +214 -0
  192. data/vendor/listen/lib/listen/adapters/bsd.rb +112 -0
  193. data/vendor/listen/lib/listen/adapters/darwin.rb +85 -0
  194. data/vendor/listen/lib/listen/adapters/linux.rb +113 -0
  195. data/vendor/listen/lib/listen/adapters/polling.rb +67 -0
  196. data/vendor/listen/lib/listen/adapters/windows.rb +87 -0
  197. data/vendor/listen/lib/listen/dependency_manager.rb +126 -0
  198. data/vendor/listen/lib/listen/directory_record.rb +371 -0
  199. data/vendor/listen/lib/listen/listener.rb +225 -0
  200. data/vendor/listen/lib/listen/multi_listener.rb +143 -0
  201. data/vendor/listen/lib/listen/turnstile.rb +28 -0
  202. data/vendor/listen/lib/listen/version.rb +3 -0
  203. data/vendor/listen/lib/listen.rb +40 -0
  204. data/vendor/listen/listen.gemspec +22 -0
  205. data/vendor/listen/spec/listen/adapter_spec.rb +183 -0
  206. data/vendor/listen/spec/listen/adapters/bsd_spec.rb +36 -0
  207. data/vendor/listen/spec/listen/adapters/darwin_spec.rb +37 -0
  208. data/vendor/listen/spec/listen/adapters/linux_spec.rb +47 -0
  209. data/vendor/listen/spec/listen/adapters/polling_spec.rb +68 -0
  210. data/vendor/listen/spec/listen/adapters/windows_spec.rb +30 -0
  211. data/vendor/listen/spec/listen/dependency_manager_spec.rb +107 -0
  212. data/vendor/listen/spec/listen/directory_record_spec.rb +1225 -0
  213. data/vendor/listen/spec/listen/listener_spec.rb +169 -0
  214. data/vendor/listen/spec/listen/multi_listener_spec.rb +174 -0
  215. data/vendor/listen/spec/listen/turnstile_spec.rb +56 -0
  216. data/vendor/listen/spec/listen_spec.rb +73 -0
  217. data/vendor/listen/spec/spec_helper.rb +21 -0
  218. data/vendor/listen/spec/support/adapter_helper.rb +629 -0
  219. data/vendor/listen/spec/support/directory_record_helper.rb +55 -0
  220. data/vendor/listen/spec/support/fixtures_helper.rb +29 -0
  221. data/vendor/listen/spec/support/listeners_helper.rb +156 -0
  222. data/vendor/listen/spec/support/platform_helper.rb +15 -0
  223. metadata +344 -271
  224. data/lib/sass/less.rb +0 -382
  225. data/lib/sass/script/bool.rb +0 -18
  226. data/lib/sass/script/funcall.rb +0 -162
  227. data/lib/sass/script/list.rb +0 -76
  228. data/lib/sass/script/literal.rb +0 -245
  229. data/lib/sass/script/variable.rb +0 -54
  230. data/lib/sass/scss/sass_parser.rb +0 -11
  231. data/test/sass/less_conversion_test.rb +0 -653
  232. data/vendor/fssm/README.markdown +0 -55
  233. data/vendor/fssm/Rakefile +0 -59
  234. data/vendor/fssm/VERSION.yml +0 -5
  235. data/vendor/fssm/example.rb +0 -9
  236. data/vendor/fssm/fssm.gemspec +0 -77
  237. data/vendor/fssm/lib/fssm/backends/fsevents.rb +0 -36
  238. data/vendor/fssm/lib/fssm/backends/inotify.rb +0 -26
  239. data/vendor/fssm/lib/fssm/backends/polling.rb +0 -25
  240. data/vendor/fssm/lib/fssm/backends/rubycocoa/fsevents.rb +0 -131
  241. data/vendor/fssm/lib/fssm/monitor.rb +0 -26
  242. data/vendor/fssm/lib/fssm/path.rb +0 -91
  243. data/vendor/fssm/lib/fssm/pathname.rb +0 -502
  244. data/vendor/fssm/lib/fssm/state/directory.rb +0 -57
  245. data/vendor/fssm/lib/fssm/state/file.rb +0 -24
  246. data/vendor/fssm/lib/fssm/support.rb +0 -63
  247. data/vendor/fssm/lib/fssm/tree.rb +0 -176
  248. data/vendor/fssm/lib/fssm.rb +0 -33
  249. data/vendor/fssm/profile/prof-cache.rb +0 -40
  250. data/vendor/fssm/profile/prof-fssm-pathname.html +0 -1231
  251. data/vendor/fssm/profile/prof-pathname.rb +0 -68
  252. data/vendor/fssm/profile/prof-plain-pathname.html +0 -988
  253. data/vendor/fssm/profile/prof.html +0 -2379
  254. data/vendor/fssm/spec/path_spec.rb +0 -75
  255. data/vendor/fssm/spec/root/duck/quack.txt +0 -0
  256. data/vendor/fssm/spec/root/file.css +0 -0
  257. data/vendor/fssm/spec/root/file.rb +0 -0
  258. data/vendor/fssm/spec/root/file.yml +0 -0
  259. data/vendor/fssm/spec/root/moo/cow.txt +0 -0
  260. data/vendor/fssm/spec/spec_helper.rb +0 -14
@@ -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