fakefs 0.4.0 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,13 +1,19 @@
1
1
  module FakeFS
2
- class FakeDir < Hash
3
- attr_accessor :name, :parent
4
- attr_reader :ctime, :mtime
2
+ class FakeDir
3
+ attr_accessor :name, :parent, :mode, :uid, :gid, :mtime, :atime
4
+ attr_reader :ctime, :content
5
5
 
6
6
  def initialize(name = nil, parent = nil)
7
- @name = name
8
- @parent = parent
9
- @ctime = Time.now
10
- @mtime = @ctime
7
+ @name = name
8
+ @parent = parent
9
+ @ctime = Time.now
10
+ @mtime = @ctime
11
+ @atime = @ctime
12
+ @mode = 0100000 + (0777 - File.umask)
13
+ @uid = Process.uid
14
+ @gid = Process.gid
15
+ @content = ""
16
+ @entries = {}
11
17
  end
12
18
 
13
19
  def entry
@@ -15,12 +21,12 @@ module FakeFS
15
21
  end
16
22
 
17
23
  def inspect
18
- "(FakeDir name:#{name.inspect} parent:#{parent.to_s.inspect} size:#{size})"
24
+ "(FakeDir name:#{name.inspect} parent:#{parent.to_s.inspect} size:#{@entries.size})"
19
25
  end
20
26
 
21
27
  def clone(parent = nil)
22
28
  clone = Marshal.load(Marshal.dump(self))
23
- clone.each do |key, value|
29
+ clone.entries.each do |value|
24
30
  value.parent = clone
25
31
  end
26
32
  clone.parent = parent if parent
@@ -37,11 +43,31 @@ module FakeFS
37
43
  end
38
44
  end
39
45
 
46
+ def empty?
47
+ @entries.empty?
48
+ end
49
+
50
+ def entries
51
+ @entries.values
52
+ end
53
+
54
+ def matches(pattern)
55
+ @entries.reject {|k,v| pattern !~ k }.values
56
+ end
57
+
58
+ def [](name)
59
+ @entries[name]
60
+ end
61
+
62
+ def []=(name, value)
63
+ @entries[name] = value
64
+ end
65
+
40
66
  def delete(node = self)
41
67
  if node == self
42
68
  parent.delete(self)
43
69
  else
44
- super(node.name)
70
+ @entries.delete(node.name)
45
71
  end
46
72
  end
47
73
  end
@@ -1,11 +1,12 @@
1
1
  module FakeFS
2
2
  class FakeFile
3
- attr_accessor :name, :parent, :content, :mtime
3
+ attr_accessor :name, :parent, :content, :mtime, :atime, :mode, :uid, :gid
4
4
  attr_reader :ctime
5
5
 
6
6
  class Inode
7
7
  def initialize(file_owner)
8
- @content = ""
8
+ #1.9.3 when possible set default external encoding
9
+ @content = "".respond_to?(:encode) ? "".encode(Encoding.default_external) : ""
9
10
  @links = [file_owner]
10
11
  end
11
12
 
@@ -34,6 +35,10 @@ module FakeFS
34
35
  @inode = Inode.new(self)
35
36
  @ctime = Time.now
36
37
  @mtime = @ctime
38
+ @atime = @ctime
39
+ @mode = 0100000 + (0666 - File.umask)
40
+ @uid = Process.uid
41
+ @gid = Process.gid
37
42
  end
38
43
 
39
44
  attr_accessor :inode
@@ -2,8 +2,6 @@ require 'stringio'
2
2
 
3
3
  module FakeFS
4
4
  class File < StringIO
5
- PATH_SEPARATOR = '/'
6
-
7
5
  MODES = [
8
6
  READ_ONLY = "r",
9
7
  READ_WRITE = "r+",
@@ -23,8 +21,8 @@ module FakeFS
23
21
  RealFile::EXCL |
24
22
  RealFile::NONBLOCK |
25
23
  RealFile::TRUNC |
26
- RealFile::NOCTTY |
27
- RealFile::SYNC
24
+ (RealFile.const_defined?(:NOCTTY) ? RealFile::NOCTTY : 0) |
25
+ (RealFile.const_defined?(:SYNC) ? RealFile::SYNC : 0)
28
26
 
29
27
  FILE_CREATION_BITMASK = RealFile::CREAT
30
28
 
@@ -69,9 +67,18 @@ module FakeFS
69
67
  end
70
68
  end
71
69
 
70
+ def self.atime(path)
71
+ if exists?(path)
72
+ FileSystem.find(path).atime
73
+ else
74
+ raise Errno::ENOENT
75
+ end
76
+ end
77
+
72
78
  def self.utime(atime, mtime, *paths)
73
79
  paths.each do |path|
74
80
  if exists?(path)
81
+ FileSystem.find(path).atime = atime
75
82
  FileSystem.find(path).mtime = mtime
76
83
  else
77
84
  raise Errno::ENOENT
@@ -140,9 +147,10 @@ module FakeFS
140
147
  symlink.target
141
148
  end
142
149
 
143
- def self.read(path)
150
+ def self.read(path, *args)
144
151
  file = new(path)
145
152
  if file.exists?
153
+ FileSystem.find(path).atime = Time.now
146
154
  file.read
147
155
  else
148
156
  raise Errno::ENOENT
@@ -224,8 +232,24 @@ module FakeFS
224
232
  return RealFile.split(path)
225
233
  end
226
234
 
235
+ def self.chmod(mode_int, filename)
236
+ FileSystem.find(filename).mode = 0100000 + mode_int
237
+ end
238
+
239
+ def self.chown(owner_int, group_int, filename)
240
+ file = FileSystem.find(filename)
241
+ owner_int.is_a?(Fixnum) or raise TypeError, "can't convert String into Integer"
242
+ group_int.is_a?(Fixnum) or raise TypeError, "can't convert String into Integer"
243
+ file.uid = owner_int
244
+ file.gid = group_int
245
+ end
246
+
247
+ def self.umask
248
+ RealFile.umask
249
+ end
250
+
227
251
  class Stat
228
- attr_reader :ctime, :mtime
252
+ attr_reader :ctime, :mtime, :atime, :mode, :uid, :gid
229
253
 
230
254
  def initialize(file, __lstat = false)
231
255
  if !File.exists?(file)
@@ -237,6 +261,10 @@ module FakeFS
237
261
  @__lstat = __lstat
238
262
  @ctime = @fake_file.ctime
239
263
  @mtime = @fake_file.mtime
264
+ @atime = @fake_file.atime
265
+ @mode = @fake_file.mode
266
+ @uid = @fake_file.uid
267
+ @gid = @fake_file.gid
240
268
  end
241
269
 
242
270
  def symlink?
@@ -247,6 +275,14 @@ module FakeFS
247
275
  File.directory?(@file)
248
276
  end
249
277
 
278
+ # assumes, like above, that all files are readable and writable
279
+ def readable?
280
+ true
281
+ end
282
+ def writable?
283
+ true
284
+ end
285
+
250
286
  def nlink
251
287
  @fake_file.links.size
252
288
  end
@@ -258,13 +294,23 @@ module FakeFS
258
294
  File.size(@file)
259
295
  end
260
296
  end
297
+
298
+ def zero?
299
+ size == 0
300
+ end
301
+
302
+ include Comparable
303
+
304
+ def <=>(other)
305
+ @mtime <=> other.mtime
306
+ end
261
307
  end
262
308
 
263
309
  attr_reader :path
264
310
 
265
311
  def initialize(path, mode = READ_ONLY, perm = nil)
266
312
  @path = path
267
- @mode = mode
313
+ @mode = mode.is_a?(Hash) ? (mode[:mode] || READ_ONLY) : mode
268
314
  @file = FileSystem.find(path)
269
315
  @autoclose = true
270
316
 
@@ -272,13 +318,19 @@ module FakeFS
272
318
 
273
319
  file_creation_mode? ? create_missing_file : check_file_existence!
274
320
 
275
- super(@file.content, mode)
321
+ super(@file.content, @mode)
276
322
  end
277
323
 
278
324
  def exists?
279
325
  true
280
326
  end
281
327
 
328
+ def write(str)
329
+ val = super(str)
330
+ @file.mtime = Time.now
331
+ val
332
+ end
333
+
282
334
  alias_method :tell=, :pos=
283
335
  alias_method :sysread, :read
284
336
  alias_method :syswrite, :write
@@ -289,6 +341,11 @@ module FakeFS
289
341
  undef_method :size
290
342
  undef_method :string
291
343
  undef_method :string=
344
+ if RUBY_PLATFORM == 'java'
345
+ undef_method :to_channel
346
+ undef_method :to_outputstream
347
+ undef_method :to_inputstream
348
+ end
292
349
 
293
350
  def ioctl(integer_cmd, arg)
294
351
  raise NotImplementedError
@@ -326,15 +383,7 @@ module FakeFS
326
383
  end
327
384
 
328
385
  def atime
329
- raise NotImplementedError
330
- end
331
-
332
- def chmod(mode_int)
333
- raise NotImplementedError
334
- end
335
-
336
- def chown(owner_int, group_int)
337
- raise NotImplementedError
386
+ self.class.atime(@path)
338
387
  end
339
388
 
340
389
  def ctime
@@ -349,6 +398,17 @@ module FakeFS
349
398
  self.class.mtime(@path)
350
399
  end
351
400
 
401
+ def chmod(mode_int)
402
+ @file.mode = 0100000 + mode_int
403
+ end
404
+
405
+ def chown(owner_int, group_int)
406
+ owner_int.is_a?(Fixnum) or raise TypeError, "can't convert String into Integer"
407
+ group_int.is_a?(Fixnum) or raise TypeError, "can't convert String into Integer"
408
+ @file.uid = owner_int
409
+ @file.gid = group_int
410
+ end
411
+
352
412
  if RUBY_VERSION >= "1.9"
353
413
  def binmode?
354
414
  raise NotImplementedError
@@ -374,6 +434,14 @@ module FakeFS
374
434
  @autoclose
375
435
  end
376
436
 
437
+ def autoclose?
438
+ @autoclose ? true : false
439
+ end
440
+
441
+ def autoclose=(autoclose)
442
+ @autoclose = autoclose
443
+ end
444
+
377
445
  alias_method :fdatasync, :flush
378
446
 
379
447
  def size
@@ -381,6 +449,11 @@ module FakeFS
381
449
  end
382
450
  end
383
451
 
452
+ if RUBY_VERSION >= "1.9.3"
453
+ def advise(advice, offset=0, len=0)
454
+ end
455
+ end
456
+
384
457
  private
385
458
 
386
459
  def check_modes!
@@ -16,7 +16,7 @@ module FakeFS
16
16
  end
17
17
 
18
18
  def files
19
- fs.values
19
+ fs.entries
20
20
  end
21
21
 
22
22
  def find(path)
@@ -46,19 +46,20 @@ module FakeFS
46
46
 
47
47
  # copies directories and files from the real filesystem
48
48
  # into our fake one
49
- def clone(path)
49
+ def clone(path, target = nil)
50
50
  path = File.expand_path(path)
51
51
  pattern = File.join(path, '**', '*')
52
52
  files = RealFile.file?(path) ? [path] : [path] + RealDir.glob(pattern, RealFile::FNM_DOTMATCH)
53
53
 
54
54
  files.each do |f|
55
+ target_path = target ? f.gsub(path, target) : f
55
56
  if RealFile.file?(f)
56
57
  FileUtils.mkdir_p(File.dirname(f))
57
- File.open(f, File::WRITE_ONLY) do |g|
58
+ File.open(target_path, File::WRITE_ONLY) do |g|
58
59
  g.print RealFile.open(f){|h| h.read }
59
60
  end
60
61
  elsif RealFile.directory?(f)
61
- FileUtils.mkdir_p(f)
62
+ FileUtils.mkdir_p(target_path)
62
63
  elsif RealFile.symlink?(f)
63
64
  FileUtils.ln_s()
64
65
  end
@@ -85,7 +86,7 @@ module FakeFS
85
86
  end
86
87
 
87
88
  def path_parts(path)
88
- path.split(File::PATH_SEPARATOR).reject { |part| part.empty? }
89
+ drop_root(path.split(File::SEPARATOR)).reject { |part| part.empty? }
89
90
  end
90
91
 
91
92
  def normalize_path(path)
@@ -103,6 +104,14 @@ module FakeFS
103
104
 
104
105
  private
105
106
 
107
+ def drop_root(path_parts)
108
+ # we need to remove parts from root dir at least for windows and jruby
109
+ return path_parts if path_parts.nil? || path_parts.empty?
110
+ root = File.expand_path('/').split(File::SEPARATOR).first
111
+ path_parts.shift if path_parts.first == root
112
+ path_parts
113
+ end
114
+
106
115
  def find_recurser(dir, parts)
107
116
  return [] unless dir.respond_to? :[]
108
117
 
@@ -113,17 +122,16 @@ module FakeFS
113
122
  when ['*']
114
123
  parts = [] # end recursion
115
124
  directories_under(dir).map do |d|
116
- d.values.select{|f| f.is_a?(FakeFile) || f.is_a?(FakeDir) }
125
+ d.entries.select{|f| f.is_a?(FakeFile) || f.is_a?(FakeDir) }
117
126
  end.flatten.uniq
118
127
  when []
119
128
  parts = [] # end recursion
120
- dir.values.flatten.uniq
129
+ dir.entries.flatten.uniq
121
130
  else
122
131
  directories_under(dir)
123
132
  end
124
133
  else
125
- regexp_pattern = /\A#{pattern.gsub('?','.').gsub('*', '.*').gsub(/\{(.*?)\}/) { "(#{$1.gsub(',', '|')})" }}\Z/
126
- dir.reject {|k,v| regexp_pattern !~ k }.values
134
+ dir.matches /\A#{pattern.gsub('.', '\.').gsub('?','.').gsub('*', '.*').gsub(/\{(.*?)\}/) { "(#{$1.gsub(',', '|')})" }}\Z/
127
135
  end
128
136
 
129
137
  if parts.empty? # we're done recursing
@@ -134,7 +142,7 @@ module FakeFS
134
142
  end
135
143
 
136
144
  def directories_under(dir)
137
- children = dir.values.select{|f| f.is_a? FakeDir}
145
+ children = dir.entries.select{|f| f.is_a? FakeDir}
138
146
  ([dir] + children + children.map{|c| directories_under(c)}).flatten.uniq
139
147
  end
140
148
  end
@@ -11,7 +11,7 @@ module FakeFS
11
11
  def mkdir(path)
12
12
  parent = path.split('/')
13
13
  parent.pop
14
- raise Errno::ENOENT, "No such file or directory - #{path}" unless parent.join == "" || FileSystem.find(parent.join('/'))
14
+ raise Errno::ENOENT, "No such file or directory - #{path}" unless parent.join == "" || parent.join == "." || FileSystem.find(parent.join('/'))
15
15
  raise Errno::EEXIST, "File exists - #{path}" if FileSystem.find(path)
16
16
  FileSystem.add(path, FakeDir.new)
17
17
  end
@@ -23,14 +23,14 @@ module FakeFS
23
23
  parent.pop
24
24
  raise Errno::ENOENT, "No such file or directory - #{l}" unless parent.join == "" || FileSystem.find(parent.join('/'))
25
25
  raise Errno::ENOENT, l unless FileSystem.find(l)
26
- raise Errno::ENOTEMPTY, l unless FileSystem.find(l).values.empty?
26
+ raise Errno::ENOTEMPTY, l unless FileSystem.find(l).empty?
27
27
  rm(l)
28
28
  end
29
29
  end
30
30
 
31
31
  def rm(list, options = {})
32
32
  Array(list).each do |path|
33
- FileSystem.delete(path) or raise Errno::ENOENT.new(path)
33
+ FileSystem.delete(path) or (!options[:force] && raise(Errno::ENOENT.new(path)))
34
34
  end
35
35
  end
36
36
 
@@ -56,50 +56,58 @@ module FakeFS
56
56
  end
57
57
 
58
58
  def cp(src, dest)
59
- dst_file = FileSystem.find(dest)
60
- src_file = FileSystem.find(src)
61
-
62
- if !src_file
63
- raise Errno::ENOENT, src
59
+ if src.is_a?(Array) && !File.directory?(dest)
60
+ raise Errno::ENOTDIR, dest
64
61
  end
65
62
 
66
- if File.directory? src_file
67
- raise Errno::EISDIR, src
68
- end
63
+ Array(src).each do |src|
64
+ dst_file = FileSystem.find(dest)
65
+ src_file = FileSystem.find(src)
69
66
 
70
- if dst_file && File.directory?(dst_file)
71
- FileSystem.add(File.join(dest, src), src_file.entry.clone(dst_file))
72
- else
73
- FileSystem.delete(dest)
74
- FileSystem.add(dest, src_file.entry.clone)
67
+ if !src_file
68
+ raise Errno::ENOENT, src
69
+ end
70
+
71
+ if File.directory? src_file
72
+ raise Errno::EISDIR, src
73
+ end
74
+
75
+ if dst_file && File.directory?(dst_file)
76
+ FileSystem.add(File.join(dest, src), src_file.entry.clone(dst_file))
77
+ else
78
+ FileSystem.delete(dest)
79
+ FileSystem.add(dest, src_file.entry.clone)
80
+ end
75
81
  end
76
82
  end
77
83
 
78
84
  def cp_r(src, dest)
79
- # This error sucks, but it conforms to the original Ruby
80
- # method.
81
- raise "unknown file type: #{src}" unless dir = FileSystem.find(src)
85
+ Array(src).each do |src|
86
+ # This error sucks, but it conforms to the original Ruby
87
+ # method.
88
+ raise "unknown file type: #{src}" unless dir = FileSystem.find(src)
82
89
 
83
- new_dir = FileSystem.find(dest)
90
+ new_dir = FileSystem.find(dest)
84
91
 
85
- if new_dir && !File.directory?(dest)
86
- raise Errno::EEXIST, dest
87
- end
92
+ if new_dir && !File.directory?(dest)
93
+ raise Errno::EEXIST, dest
94
+ end
88
95
 
89
- if !new_dir && !FileSystem.find(dest+'/../')
90
- raise Errno::ENOENT, dest
91
- end
96
+ if !new_dir && !FileSystem.find(dest+'/../')
97
+ raise Errno::ENOENT, dest
98
+ end
92
99
 
93
- # This last bit is a total abuse and should be thought hard
94
- # about and cleaned up.
95
- if new_dir
96
- if src[-2..-1] == '/.'
97
- dir.values.each{|f| new_dir[f.name] = f.clone(new_dir) }
100
+ # This last bit is a total abuse and should be thought hard
101
+ # about and cleaned up.
102
+ if new_dir
103
+ if src[-2..-1] == '/.'
104
+ dir.entries.each{|f| new_dir[f.name] = f.clone(new_dir) }
105
+ else
106
+ new_dir[dir.name] = dir.entry.clone(new_dir)
107
+ end
98
108
  else
99
- new_dir[dir.name] = dir.entry.clone(new_dir)
109
+ FileSystem.add(dest, dir.entry.clone)
100
110
  end
101
- else
102
- FileSystem.add(dest, dir.entry.clone)
103
111
  end
104
112
  end
105
113
 
@@ -118,7 +126,11 @@ module FakeFS
118
126
  def chown(user, group, list, options={})
119
127
  list = Array(list)
120
128
  list.each do |f|
121
- unless File.exists?(f)
129
+ if File.exists?(f)
130
+ uid = (user.to_s.match(/[0-9]+/) ? user.to_i : Etc.getpwnam(user).uid)
131
+ gid = (group.to_s.match(/[0-9]+/) ? group.to_i : Etc.getgrnam(group).gid)
132
+ File.chown(uid, gid, f)
133
+ else
122
134
  raise Errno::ENOENT, f
123
135
  end
124
136
  end
@@ -126,17 +138,48 @@ module FakeFS
126
138
  end
127
139
 
128
140
  def chown_R(user, group, list, options={})
129
- chown(user, group, list, options={})
141
+ list = Array(list)
142
+ list.each do |file|
143
+ chown(user, group, file)
144
+ [FileSystem.find("#{file}/**/**")].flatten.each do |f|
145
+ chown(user, group, f.to_s)
146
+ end
147
+ end
148
+ list
149
+ end
150
+
151
+ def chmod(mode, list, options={})
152
+ list = Array(list)
153
+ list.each do |f|
154
+ if File.exists?(f)
155
+ File.chmod(mode, f)
156
+ else
157
+ raise Errno::ENOENT, f
158
+ end
159
+ end
160
+ list
161
+ end
162
+
163
+ def chmod_R(mode, list, options={})
164
+ list = Array(list)
165
+ list.each do |file|
166
+ chmod(mode, file)
167
+ [FileSystem.find("#{file}/**/**")].flatten.each do |f|
168
+ chmod(mode, f.to_s)
169
+ end
170
+ end
171
+ list
130
172
  end
131
173
 
132
174
  def touch(list, options={})
133
175
  Array(list).each do |f|
134
- directory = File.dirname(f)
135
- # FIXME this explicit check for '.' shouldn't need to happen
136
- if File.exists?(directory) || directory == '.'
137
- FileSystem.add(f, FakeFile.new)
176
+ if fs = FileSystem.find(f)
177
+ now = Time.now
178
+ fs.mtime = now
179
+ fs.atime = now
138
180
  else
139
- raise Errno::ENOENT, f
181
+ f = File.open(f, 'w')
182
+ f.close
140
183
  end
141
184
  end
142
185
  end