perennial 0.2.2.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,37 @@
1
+ FakeFS
2
+ ======
3
+
4
+ Mocha is great. But when your library is all about manipulating the
5
+ filesystem, you really want to test the behavior and not the implementation.
6
+
7
+ If you're mocking and stubbing every call to FileUtils or File, you're
8
+ tightly coupling your tests with the implementation.
9
+
10
+ def test_creates_directory
11
+ FileUtils.expects(:mkdir).with("directory").once
12
+ Library.add "directory"
13
+ end
14
+
15
+ The above test will break if we decide to use `mkdir_p` in our code. Refactoring
16
+ code shouldn't necessitate refactoring tests.
17
+
18
+ With FakeFS:
19
+
20
+ def test_creates_directory
21
+ Library.add "directory"
22
+ assert File.directory?("directory")
23
+ end
24
+
25
+ Woot.
26
+
27
+ How is this different than MockFS?
28
+ ----------------------------------
29
+
30
+ FakeFS provides a test suite and works with symlinks. It's also strictly a
31
+ test-time dependency: your actual library does not need to use or know about
32
+ FakeFS.
33
+
34
+ Authors
35
+ -------
36
+
37
+ Chris Wanstrath [chris@ozmm.org]
@@ -0,0 +1,3 @@
1
+ task :default do
2
+ exec "ruby test/fakefs_test.rb"
3
+ end
@@ -0,0 +1,448 @@
1
+ require 'fileutils'
2
+ require 'pathname'
3
+
4
+ RealFile = File
5
+ RealFileUtils = FileUtils
6
+ RealDir = Dir
7
+ RealFileUtils::Dir = RealDir
8
+ RealFileUtils::File = RealFile
9
+
10
+ module FakeFS
11
+ module FileUtils
12
+ extend self
13
+
14
+ def mkdir_p(path)
15
+ FileSystem.add(path, MockDir.new)
16
+ end
17
+
18
+ def rm(path)
19
+ FileSystem.delete(path)
20
+ end
21
+ alias_method :rm_rf, :rm
22
+
23
+ def ln_s(target, path)
24
+ raise Errno::EEXIST, path if FileSystem.find(path)
25
+ FileSystem.add(path, MockSymlink.new(target))
26
+ end
27
+
28
+ def cp(src, dest)
29
+ dst_file = FileSystem.find(dest)
30
+ src_file = FileSystem.find(src)
31
+
32
+ if !src_file
33
+ raise Errno::ENOENT, src
34
+ end
35
+
36
+ if File.directory? src_file
37
+ raise Errno::EISDIR, src
38
+ end
39
+
40
+ if dst_file and File.directory?(dst_file)
41
+ FileSystem.add(File.join(dest, src), src_file.entry.clone(dst_file))
42
+ else
43
+ FileSystem.delete(dest)
44
+ FileSystem.add(dest, src_file.entry.clone)
45
+ end
46
+ end
47
+
48
+ def cp_r(src, dest)
49
+ # This error sucks, but it conforms to the original Ruby
50
+ # method.
51
+ raise "unknown file type: #{src}" unless dir = FileSystem.find(src)
52
+
53
+ new_dir = FileSystem.find(dest)
54
+
55
+ if new_dir && !File.directory?(dest)
56
+ raise Errno::EEXIST, dest
57
+ end
58
+
59
+ if !new_dir && !FileSystem.find(dest+'/../')
60
+ raise Errno::ENOENT, dest
61
+ end
62
+
63
+ # This last bit is a total abuse and should be thought hard
64
+ # about and cleaned up.
65
+ if new_dir
66
+ if src[-2..-1] == '/.'
67
+ dir.values.each{|f| new_dir[f.name] = f.clone(new_dir) }
68
+ else
69
+ new_dir[dir.name] = dir.entry.clone(new_dir)
70
+ end
71
+ else
72
+ FileSystem.add(dest, dir.entry.clone)
73
+ end
74
+ end
75
+
76
+ def mv(src, dest)
77
+ if target = FileSystem.find(src)
78
+ FileSystem.add(dest, target.entry.clone)
79
+ FileSystem.delete(src)
80
+ else
81
+ raise Errno::ENOENT, src
82
+ end
83
+ end
84
+
85
+ def chown(user, group, list, options={})
86
+ list = Array(list)
87
+ list.each do |f|
88
+ unless File.exists?(f)
89
+ raise Errno::ENOENT, f
90
+ end
91
+ end
92
+ list
93
+ end
94
+
95
+ def chown_R(user, group, list, options={})
96
+ chown(user, group, list, options={})
97
+ end
98
+
99
+ def touch(list, options={})
100
+ Array(list).each do |f|
101
+ directory = File.dirname(f)
102
+ # FIXME this explicit check for '.' shouldn't need to happen
103
+ if File.exists?(directory) || directory == '.'
104
+ FileSystem.add(f, MockFile.new)
105
+ else
106
+ raise Errno::ENOENT, f
107
+ end
108
+ end
109
+ end
110
+ end
111
+
112
+ class File
113
+ PATH_SEPARATOR = '/'
114
+
115
+ def self.join(*parts)
116
+ parts * PATH_SEPARATOR
117
+ end
118
+
119
+ def self.exist?(path)
120
+ FileSystem.find(path) || false
121
+ end
122
+
123
+ class << self
124
+ alias_method :exists?, :exist?
125
+ end
126
+
127
+ def self.directory?(path)
128
+ if path.respond_to? :entry
129
+ path.entry.is_a? MockDir
130
+ else
131
+ result = FileSystem.find(path)
132
+ result ? result.entry.is_a?(MockDir) : false
133
+ end
134
+ end
135
+
136
+ def self.symlink?(path)
137
+ if path.respond_to? :entry
138
+ path.is_a? MockSymlink
139
+ else
140
+ FileSystem.find(path).is_a? MockSymlink
141
+ end
142
+ end
143
+
144
+ def self.file?(path)
145
+ if path.respond_to? :entry
146
+ path.entry.is_a? MockFile
147
+ else
148
+ result = FileSystem.find(path)
149
+ result ? result.entry.is_a?(MockFile) : false
150
+ end
151
+ end
152
+
153
+ def self.expand_path(*args)
154
+ RealFile.expand_path(*args)
155
+ end
156
+
157
+ def self.basename(*args)
158
+ RealFile.basename(*args)
159
+ end
160
+
161
+ def self.dirname(path)
162
+ RealFile.dirname(path)
163
+ end
164
+
165
+ def self.readlink(path)
166
+ symlink = FileSystem.find(path)
167
+ FileSystem.find(symlink.target).to_s
168
+ end
169
+
170
+ def self.open(path, mode='r')
171
+ if block_given?
172
+ yield new(path, mode)
173
+ else
174
+ new(path, mode)
175
+ end
176
+ end
177
+
178
+ def self.read(path)
179
+ file = new(path)
180
+ if file.exists?
181
+ file.read
182
+ else
183
+ raise Errno::ENOENT
184
+ end
185
+ end
186
+
187
+ def self.readlines(path)
188
+ read(path).split("\n")
189
+ end
190
+
191
+ attr_reader :path
192
+ def initialize(path, mode = nil)
193
+ @path = path
194
+ @mode = mode
195
+ @file = FileSystem.find(path)
196
+ @open = true
197
+ end
198
+
199
+ def close
200
+ @open = false
201
+ end
202
+
203
+ def read
204
+ raise IOError.new('closed stream') unless @open
205
+ @file.content
206
+ end
207
+
208
+ def exists?
209
+ @file
210
+ end
211
+
212
+ def puts(content)
213
+ write(content + "\n")
214
+ end
215
+
216
+ def write(content)
217
+ raise IOError.new('closed stream') unless @open
218
+
219
+ if !File.exists?(@path)
220
+ @file = FileSystem.add(path, MockFile.new)
221
+ end
222
+
223
+ @file.content += content
224
+ end
225
+ alias_method :print, :write
226
+ alias_method :<<, :write
227
+
228
+ def flush; self; end
229
+ end
230
+
231
+ class Dir
232
+ def self.glob(pattern)
233
+ if pattern[-1,1] == '*'
234
+ blk = proc { |entry| entry.to_s }
235
+ else
236
+ blk = proc { |entry| entry[1].parent.to_s }
237
+ end
238
+ (FileSystem.find(pattern) || []).map(&blk).uniq.sort
239
+ end
240
+
241
+ def self.[](pattern)
242
+ glob(pattern)
243
+ end
244
+
245
+ def self.chdir(dir, &blk)
246
+ FileSystem.chdir(dir, &blk)
247
+ end
248
+ end
249
+
250
+ module FileSystem
251
+ extend self
252
+
253
+ def dir_levels
254
+ @dir_levels ||= []
255
+ end
256
+
257
+ def fs
258
+ @fs ||= MockDir.new('.')
259
+ end
260
+
261
+ def clear
262
+ @dir_levels = nil
263
+ @fs = nil
264
+ end
265
+
266
+ def files
267
+ fs.values
268
+ end
269
+
270
+ def find(path)
271
+ parts = path_parts(normalize_path(path))
272
+
273
+ target = parts[0...-1].inject(fs) do |dir, part|
274
+ dir[part] || {}
275
+ end
276
+
277
+ case parts.last
278
+ when '*'
279
+ target.values
280
+ else
281
+ target[parts.last]
282
+ end
283
+ end
284
+
285
+ def add(path, object=MockDir.new)
286
+ parts = path_parts(normalize_path(path))
287
+
288
+ d = parts[0...-1].inject(fs) do |dir, part|
289
+ dir[part] ||= MockDir.new(part, dir)
290
+ end
291
+
292
+ object.name = parts.last
293
+ object.parent = d
294
+ d[parts.last] ||= object
295
+ end
296
+
297
+ # copies directories and files from the real filesystem
298
+ # into our fake one
299
+ def clone(path)
300
+ path = File.expand_path(path)
301
+ pattern = File.join(path, '**', '*')
302
+ files = RealFile.file?(path) ? [path] : [path] + RealDir.glob(pattern, RealFile::FNM_DOTMATCH)
303
+
304
+ files.each do |f|
305
+ if RealFile.file?(f)
306
+ FileUtils.mkdir_p(File.dirname(f))
307
+ File.open(f, 'w') do |g|
308
+ g.print RealFile.open(f){|h| h.read }
309
+ end
310
+ elsif RealFile.directory?(f)
311
+ FileUtils.mkdir_p(f)
312
+ elsif RealFile.symlink?(f)
313
+ FileUtils.ln_s()
314
+ end
315
+ end
316
+ end
317
+
318
+ def delete(path)
319
+ if dir = FileSystem.find(path)
320
+ dir.parent.delete(dir.name)
321
+ end
322
+ end
323
+
324
+ def chdir(dir, &blk)
325
+ new_dir = find(dir)
326
+ dir_levels.push dir if blk
327
+
328
+ raise Errno::ENOENT, dir unless new_dir
329
+
330
+ dir_levels.push dir if !blk
331
+ blk.call if blk
332
+ ensure
333
+ dir_levels.pop if blk
334
+ end
335
+
336
+ def path_parts(path)
337
+ path.split(File::PATH_SEPARATOR).reject { |part| part.empty? }
338
+ end
339
+
340
+ def normalize_path(path)
341
+ if Pathname.new(path).absolute?
342
+ File.expand_path(path)
343
+ else
344
+ parts = dir_levels + [path]
345
+ File.expand_path(File.join(*parts))
346
+ end
347
+ end
348
+
349
+ def current_dir
350
+ find(normalize_path('.'))
351
+ end
352
+ end
353
+
354
+ class MockFile
355
+ attr_accessor :name, :parent, :content
356
+
357
+ def initialize(name = nil, parent = nil)
358
+ @name = name
359
+ @parent = parent
360
+ @content = ''
361
+ end
362
+
363
+ def clone(parent = nil)
364
+ clone = super()
365
+ clone.parent = parent if parent
366
+ clone
367
+ end
368
+
369
+ def entry
370
+ self
371
+ end
372
+
373
+ def inspect
374
+ "(MockFile name:#{name.inspect} parent:#{parent.to_s.inspect} size:#{content.size})"
375
+ end
376
+
377
+ def to_s
378
+ File.join(parent.to_s, name)
379
+ end
380
+ end
381
+
382
+ class MockDir < Hash
383
+ attr_accessor :name, :parent
384
+
385
+ def initialize(name = nil, parent = nil)
386
+ @name = name
387
+ @parent = parent
388
+ end
389
+
390
+ def entry
391
+ self
392
+ end
393
+
394
+ def inspect
395
+ "(MockDir name:#{name.inspect} parent:#{parent.to_s.inspect} size:#{size})"
396
+ end
397
+
398
+ def clone(parent = nil)
399
+ clone = Marshal.load(Marshal.dump(self))
400
+ clone.each do |key, value|
401
+ value.parent = clone
402
+ end
403
+ clone.parent = parent if parent
404
+ clone
405
+ end
406
+
407
+ def to_s
408
+ if parent && parent.to_s != '.'
409
+ File.join(parent.to_s, name)
410
+ elsif parent && parent.to_s == '.'
411
+ "#{File::PATH_SEPARATOR}#{name}"
412
+ else
413
+ name
414
+ end
415
+ end
416
+ end
417
+
418
+ class MockSymlink
419
+ attr_accessor :name, :target
420
+ alias_method :to_s, :name
421
+
422
+ def initialize(target)
423
+ @target = target
424
+ end
425
+
426
+ def inspect
427
+ "symlink(#{target.split('/').last})"
428
+ end
429
+
430
+ def entry
431
+ FileSystem.find(target)
432
+ end
433
+
434
+ def method_missing(*args, &block)
435
+ entry.send(*args, &block)
436
+ end
437
+ end
438
+ end
439
+
440
+ Object.class_eval do
441
+ remove_const(:Dir)
442
+ remove_const(:File)
443
+ remove_const(:FileUtils)
444
+ end
445
+
446
+ File = FakeFS::File
447
+ FileUtils = FakeFS::FileUtils
448
+ Dir = FakeFS::Dir