fakefs 0.1.0 → 0.2.1

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.
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ *.sw?
data/CONTRIBUTORS ADDED
@@ -0,0 +1,12 @@
1
+ * Chris Wanstrath
2
+ * Jeff Hodges
3
+ * Scott Taylor
4
+ * Pat Nakajima
5
+ * Myles Eftos
6
+ * Matt Freels
7
+ * Nick Quaranto
8
+ * Tymon Tobolski
9
+ * msassak
10
+ * Rob Sanheim
11
+ * Jon Yurek
12
+ * David Reese
data/README.markdown CHANGED
@@ -4,7 +4,7 @@ FakeFS
4
4
  Mocha is great. But when your library is all about manipulating the
5
5
  filesystem, you really want to test the behavior and not the implementation.
6
6
 
7
- If you're mocking and stubbing every call to FileUtils or File, you're
7
+ If you're mocking and stubbing every call to FileUtils or File, you're
8
8
  tightly coupling your tests with the implementation.
9
9
 
10
10
  def test_creates_directory
@@ -48,21 +48,54 @@ Don't Fake the FS Immediately
48
48
  end
49
49
 
50
50
 
51
+ RSpec
52
+ -----------------------------
53
+ The above approach works with RSpec as well. In addition to this you may use the
54
+ 'use_fakefs' macro to turn FakeFS on and off in a given example group. See
55
+ lib/spec_helpers for more details on it's usage.
56
+
57
+
51
58
  How is this different than MockFS?
52
59
  ----------------------------------
53
60
 
54
- FakeFS provides a test suite and works with symlinks. It's also strictly a
61
+ FakeFS provides a test suite and works with symlinks. It's also strictly a
55
62
  test-time dependency: your actual library does not need to use or know about
56
63
  FakeFS.
57
64
 
58
65
 
66
+ Caveats
67
+ -------
68
+
69
+ FakeFS internally uses the `Pathname` and `FileUtils` constants. If you use
70
+ these in your app, be certain you're properly requiring them and not counting
71
+ on FakeFS' own require.
72
+
73
+
59
74
  Speed?
60
75
  ------
61
- http://gist.github.com/156091
76
+ <http://gist.github.com/156091>
62
77
 
63
78
 
64
- Authors
65
- -------
79
+ Installation
80
+ ------------
81
+
82
+ ### [Gemcutter](http://gemcutter.org/)
83
+
84
+ $ gem install fakefs
85
+
86
+ ### [Rip](http://hellorip.com)
87
+
88
+ $ rip install git://github.com/defunkt/fakefs.git
89
+
90
+
91
+ Meta
92
+ ----
66
93
 
67
- * Chris Wanstrath [chris@ozmm.org]
68
- * Pat Nakajima [http://github.com/nakajima]
94
+ * Code: `git clone git://github.com/defunkt/fakefs.git`
95
+ * Home: <http://github.com/defunkt/fakefs>
96
+ * Docs: <http://defunkt.github.com/fakefs>
97
+ * Bugs: <http://github.com/defunkt/fakefs/issues>
98
+ * List: <http://groups.google.com/group/fakefs>
99
+ * Test: <http://runcoderun.com/defunkt/fakefs>
100
+ * Gems: <http://gemcutter.org/gems/fakefs>
101
+ * Boss: Chris Wanstrath :: <http://github.com/defunkt>
data/Rakefile CHANGED
@@ -1,30 +1,16 @@
1
- task :default do
2
- Dir['test/*_test.rb'].each { |file| require file }
1
+ desc "Run tests"
2
+ task :test do
3
+ Dir['test/**/*_test.rb'].each { |file| require file }
3
4
  end
4
5
 
6
+ task :default => :test
7
+
5
8
  begin
6
9
  require 'jeweler'
7
10
 
8
- # We're not putting VERSION or VERSION.yml in the root,
9
- # so we have to help Jeweler find our version.
10
11
  $LOAD_PATH.unshift File.dirname(__FILE__) + '/lib'
11
12
  require 'fakefs/version'
12
13
 
13
- FakeFS::Version.instance_eval do
14
- def refresh
15
- end
16
- end
17
-
18
- class Jeweler
19
- def version_helper
20
- FakeFS::Version
21
- end
22
-
23
- def version_exists?
24
- true
25
- end
26
- end
27
-
28
14
  Jeweler::Tasks.new do |gemspec|
29
15
  gemspec.name = "fakefs"
30
16
  gemspec.summary = "A fake filesystem. Use it in your tests."
@@ -33,8 +19,29 @@ begin
33
19
  gemspec.description = "A fake filesystem. Use it in your tests."
34
20
  gemspec.authors = ["Chris Wanstrath"]
35
21
  gemspec.has_rdoc = false
22
+ gemspec.version = FakeFS::Version.to_s
36
23
  end
37
24
  rescue LoadError
38
25
  puts "Jeweler not available."
39
- puts "Install it with: gem install technicalpickles-jeweler"
26
+ puts "Install it with: gem install jeweler"
27
+ end
28
+
29
+ begin
30
+ require 'sdoc_helpers'
31
+ rescue LoadError
32
+ puts "sdoc support not enabled. Please gem install sdoc-helpers."
33
+ end
34
+
35
+ desc "Build a gem"
36
+ task :gem => [ :gemspec, :build ]
37
+
38
+ desc "Push a new version to Gemcutter"
39
+ task :publish => [ :gemspec, :build ] do
40
+ abort("Tests failed!") unless system("rake test")
41
+ system "git tag v#{FakeFS::Version}"
42
+ system "git push origin v#{FakeFS::Version}"
43
+ system "git push origin master"
44
+ system "gem push pkg/fakefs-#{FakeFS::Version}.gem"
45
+ system "git clean -fd"
46
+ exec "rake pages"
40
47
  end
data/lib/fakefs/base.rb CHANGED
@@ -32,6 +32,7 @@ def FakeFS
32
32
  return ::FakeFS unless block_given?
33
33
  ::FakeFS.activate!
34
34
  yield
35
- ::FakeFS.deactivate!
35
+ ensure
36
+ ::FakeFS.deactivate!
36
37
  end
37
38
 
data/lib/fakefs/dir.rb CHANGED
@@ -1,7 +1,55 @@
1
1
  module FakeFS
2
2
  class Dir
3
- def self.glob(pattern)
4
- [FileSystem.find(pattern) || []].flatten.map{|e| e.to_s}.sort
3
+ include Enumerable
4
+
5
+ def initialize(string)
6
+ raise Errno::ENOENT, string unless FileSystem.find(string)
7
+ @path = string
8
+ @open = true
9
+ @pointer = 0
10
+ @contents = [ '.', '..', ] + FileSystem.find(@path).values
11
+ end
12
+
13
+ def close
14
+ @open = false
15
+ @pointer = nil
16
+ @contents = nil
17
+ nil
18
+ end
19
+
20
+ def each(&block)
21
+ while f = read
22
+ yield f
23
+ end
24
+ end
25
+
26
+ def path
27
+ @path
28
+ end
29
+
30
+ def pos
31
+ @pointer
32
+ end
33
+
34
+ def pos=(integer)
35
+ @pointer = integer
36
+ end
37
+
38
+ def read
39
+ raise IOError, "closed directory" if @pointer == nil
40
+ n = @contents[@pointer]
41
+ @pointer += 1
42
+ n.to_s.gsub(path + '/', '') if n
43
+ end
44
+
45
+ def rewind
46
+ @pointer = 0
47
+ end
48
+
49
+ def seek(integer)
50
+ raise IOError, "closed directory" if @pointer == nil
51
+ @pointer = integer
52
+ @contents[integer]
5
53
  end
6
54
 
7
55
  def self.[](pattern)
@@ -12,12 +60,55 @@ module FakeFS
12
60
  FileSystem.chdir(dir, &blk)
13
61
  end
14
62
 
63
+ def self.chroot(string)
64
+ # Not implemented yet
65
+ end
66
+
67
+ def self.delete(string)
68
+ raise SystemCallError, "No such file or directory - #{string}" unless FileSystem.find(string).values.empty?
69
+ FileSystem.delete(string)
70
+ end
71
+
72
+ def self.entries(dirname)
73
+ raise SystemCallError, dirname unless FileSystem.find(dirname)
74
+ Dir.new(dirname).map { |file| File.basename(file) }
75
+ end
76
+
77
+ def self.foreach(dirname, &block)
78
+ Dir.open(dirname) { |file| yield file }
79
+ end
80
+
81
+ def self.glob(pattern)
82
+ [FileSystem.find(pattern) || []].flatten.map{|e| e.to_s}.sort
83
+ end
84
+
85
+ def self.mkdir(string, integer = 0)
86
+ parent = string.split('/')
87
+ parent.pop
88
+ raise Errno::ENOENT, "No such file or directory - #{string}" unless parent.join == "" || FileSystem.find(parent.join('/'))
89
+ FileUtils.mkdir_p(string)
90
+ end
91
+
92
+ def self.open(string, &block)
93
+ if block_given?
94
+ Dir.new(string).each { |file| yield(file) }
95
+ else
96
+ Dir.new(string)
97
+ end
98
+ end
99
+
100
+ def self.tmpdir
101
+ '/tmp'
102
+ end
103
+
15
104
  def self.pwd
16
105
  FileSystem.current_dir.to_s
17
106
  end
18
107
 
19
108
  class << self
20
109
  alias_method :getwd, :pwd
110
+ alias_method :rmdir, :delete
111
+ alias_method :unlink, :delete
21
112
  end
22
113
  end
23
114
  end
@@ -33,5 +33,13 @@ module FakeFS
33
33
  name
34
34
  end
35
35
  end
36
+
37
+ def delete(node = self)
38
+ if node == self
39
+ parent.delete(self)
40
+ else
41
+ super(node.name)
42
+ end
43
+ end
36
44
  end
37
45
  end
@@ -1,16 +1,61 @@
1
1
  module FakeFS
2
2
  class FakeFile
3
- attr_accessor :name, :parent, :content
3
+ attr_accessor :name, :parent, :content, :mtime
4
+
5
+ class Inode
6
+ def initialize(file_owner)
7
+ @content = ""
8
+ @links = [file_owner]
9
+ end
10
+
11
+ attr_accessor :content
12
+ attr_accessor :links
13
+
14
+ def link(file)
15
+ links << file unless links.include?(file)
16
+ file.inode = self
17
+ end
18
+
19
+ def unlink(file)
20
+ links.delete(file)
21
+ end
22
+
23
+ def clone
24
+ clone = super
25
+ clone.content = content.dup
26
+ clone
27
+ end
28
+ end
4
29
 
5
30
  def initialize(name = nil, parent = nil)
6
- @name = name
31
+ @name = name
7
32
  @parent = parent
8
- @content = ''
33
+ @inode = Inode.new(self)
34
+ @mtime = Time.now
35
+ end
36
+
37
+ attr_accessor :inode
38
+
39
+ def content
40
+ @inode.content
41
+ end
42
+
43
+ def content=(str)
44
+ @inode.content = str
45
+ end
46
+
47
+ def links
48
+ @inode.links
49
+ end
50
+
51
+ def link(other_file)
52
+ @inode.link(other_file)
9
53
  end
10
54
 
11
55
  def clone(parent = nil)
12
56
  clone = super()
13
57
  clone.parent = parent if parent
58
+ clone.inode = inode.clone
14
59
  clone
15
60
  end
16
61
 
@@ -25,5 +70,10 @@ module FakeFS
25
70
  def to_s
26
71
  File.join(parent.to_s, name)
27
72
  end
73
+
74
+ def delete
75
+ inode.unlink(self)
76
+ parent.delete(self)
77
+ end
28
78
  end
29
79
  end
@@ -15,12 +15,18 @@ module FakeFS
15
15
  FileSystem.find(target)
16
16
  end
17
17
 
18
- def method_missing(*args, &block)
19
- entry.send(*args, &block)
18
+ def delete
19
+ parent.delete(self)
20
20
  end
21
21
 
22
22
  def respond_to?(method)
23
23
  entry.respond_to?(method)
24
24
  end
25
+
26
+ private
27
+
28
+ def method_missing(*args, &block)
29
+ entry.send(*args, &block)
30
+ end
25
31
  end
26
32
  end
data/lib/fakefs/file.rb CHANGED
@@ -2,6 +2,34 @@ module FakeFS
2
2
  class File
3
3
  PATH_SEPARATOR = '/'
4
4
 
5
+ MODES = [
6
+ READ_ONLY = "r",
7
+ READ_WRITE = "r+",
8
+ WRITE_ONLY = "w",
9
+ READ_WRITE_TRUNCATE = "w+",
10
+ APPEND_WRITE_ONLY = "a",
11
+ APPEND_READ_WRITE = "a+"
12
+ ]
13
+
14
+ FILE_CREATION_MODES = MODES - [READ_ONLY, READ_WRITE]
15
+
16
+ MODE_BITMASK = RealFile::RDONLY |
17
+ RealFile::WRONLY |
18
+ RealFile::RDWR |
19
+ RealFile::APPEND |
20
+ RealFile::CREAT |
21
+ RealFile::EXCL |
22
+ RealFile::NONBLOCK |
23
+ RealFile::TRUNC |
24
+ RealFile::NOCTTY |
25
+ RealFile::SYNC
26
+
27
+ FILE_CREATION_BITMASK = RealFile::CREAT
28
+
29
+ def self.extname(path)
30
+ RealFile.extname(path)
31
+ end
32
+
5
33
  def self.join(*parts)
6
34
  parts * PATH_SEPARATOR
7
35
  end
@@ -12,6 +40,30 @@ module FakeFS
12
40
 
13
41
  class << self
14
42
  alias_method :exists?, :exist?
43
+
44
+ # Assuming that everyone can read and write files
45
+ alias_method :readable?, :exist?
46
+ alias_method :writable?, :exist?
47
+ end
48
+
49
+ def self.mtime(path)
50
+ if exists?(path)
51
+ FileSystem.find(path).mtime
52
+ else
53
+ raise Errno::ENOENT
54
+ end
55
+ end
56
+
57
+ def self.size(path)
58
+ read(path).length
59
+ end
60
+
61
+ def self.size?(path)
62
+ if exists?(path) && !size(path).zero?
63
+ true
64
+ else
65
+ nil
66
+ end
15
67
  end
16
68
 
17
69
  def self.const_missing(name)
@@ -61,11 +113,11 @@ module FakeFS
61
113
  FileSystem.find(symlink.target).to_s
62
114
  end
63
115
 
64
- def self.open(path, mode='r')
116
+ def self.open(path, mode=READ_ONLY, perm = 0644)
65
117
  if block_given?
66
- yield new(path, mode)
118
+ yield new(path, mode, perm)
67
119
  else
68
- new(path, mode)
120
+ new(path, mode, perm)
69
121
  end
70
122
  end
71
123
 
@@ -82,43 +134,149 @@ module FakeFS
82
134
  read(path).split("\n")
83
135
  end
84
136
 
137
+ def self.link(source, dest)
138
+ if directory?(source)
139
+ raise Errno::EPERM, "Operation not permitted - #{source} or #{dest}"
140
+ end
141
+
142
+ if !exists?(source)
143
+ raise Errno::ENOENT, "No such file or directory - #{source} or #{dest}"
144
+ end
145
+
146
+ if exists?(dest)
147
+ raise Errno::EEXIST, "File exists - #{source} or #{dest}"
148
+ end
149
+
150
+ source = FileSystem.find(source)
151
+ dest = FileSystem.add(dest, source.entry.clone)
152
+ source.link(dest)
153
+
154
+ 0
155
+ end
156
+
157
+ def self.delete(file_name, *additional_file_names)
158
+ if !exists?(file_name)
159
+ raise Errno::ENOENT, "No such file or directory - #{file_name}"
160
+ end
161
+
162
+ FileUtils.rm(file_name)
163
+
164
+ additional_file_names.each do |file_name|
165
+ FileUtils.rm(file_name)
166
+ end
167
+
168
+ additional_file_names.size + 1
169
+ end
170
+
171
+ class << self
172
+ alias_method :unlink, :delete
173
+ end
174
+
175
+ def self.symlink(source, dest)
176
+ FileUtils.ln_s(source, dest)
177
+ end
178
+
179
+ def self.stat(file)
180
+ File::Stat.new(file)
181
+ end
182
+
183
+ class Stat
184
+ def initialize(file)
185
+ if !File.exists?(file)
186
+ raise(Errno::ENOENT, "No such file or directory - #{file}")
187
+ end
188
+
189
+ @file = file
190
+ end
191
+
192
+ def symlink?
193
+ File.symlink?(@file)
194
+ end
195
+
196
+ def directory?
197
+ File.directory?(@file)
198
+ end
199
+
200
+ def nlink
201
+ FileSystem.find(@file).links.size
202
+ end
203
+ end
204
+
85
205
  attr_reader :path
86
- def initialize(path, mode = nil)
206
+
207
+ def initialize(path, mode = READ_ONLY, perm = nil)
87
208
  @path = path
88
209
  @mode = mode
89
210
  @file = FileSystem.find(path)
90
211
  @open = true
212
+
213
+ check_modes!
214
+
215
+ file_creation_mode? ? create_missing_file : check_file_existence!
216
+
217
+ @stream = StringIO.new(@file.content, mode)
91
218
  end
92
219
 
93
220
  def close
94
221
  @open = false
95
222
  end
96
223
 
97
- def read
98
- raise IOError.new('closed stream') unless @open
99
- @file.content
224
+ def read(chunk = nil)
225
+ @stream.read(chunk)
226
+ end
227
+
228
+ def rewind
229
+ @stream.rewind
100
230
  end
101
231
 
102
232
  def exists?
103
- @file
233
+ true
104
234
  end
105
235
 
106
- def puts(content)
107
- write(content + "\n")
236
+ def puts(*content)
237
+ @stream.puts(*content)
108
238
  end
109
239
 
110
240
  def write(content)
111
- raise IOError.new('closed stream') unless @open
112
-
113
- if !File.exists?(@path)
114
- @file = FileSystem.add(path, FakeFile.new)
115
- end
116
-
117
- @file.content += content
241
+ @stream.write(content)
118
242
  end
119
243
  alias_method :print, :write
120
244
  alias_method :<<, :write
121
245
 
122
246
  def flush; self; end
247
+
248
+ def seek(amount, whence = SEEK_SET)
249
+ @stream.seek(amount, whence)
250
+ end
251
+
252
+ private
253
+
254
+ def check_modes!
255
+ StringIO.new("", @mode)
256
+ end
257
+
258
+ def check_file_existence!
259
+ unless @file
260
+ raise Errno::ENOENT, "No such file or directory - #{@file}"
261
+ end
262
+ end
263
+
264
+ def file_creation_mode?
265
+ mode_in?(FILE_CREATION_MODES) || mode_in_bitmask?(FILE_CREATION_BITMASK)
266
+ end
267
+
268
+ def mode_in?(list)
269
+ list.any? { |element| @mode.include?(element) } if @mode.respond_to?(:include?)
270
+ end
271
+
272
+ def mode_in_bitmask?(mask)
273
+ (@mode & mask) != 0 if @mode.is_a?(Integer)
274
+ end
275
+
276
+ def create_missing_file
277
+ if !File.exists?(@path)
278
+ @file = FileSystem.add(path, FakeFile.new)
279
+ end
280
+ end
123
281
  end
124
282
  end
@@ -22,9 +22,9 @@ module FakeFS
22
22
  def find(path)
23
23
  parts = path_parts(normalize_path(path))
24
24
  return fs if parts.empty? # '/'
25
-
25
+
26
26
  entries = find_recurser(fs, parts).flatten
27
-
27
+
28
28
  case entries.length
29
29
  when 0 then nil
30
30
  when 1 then entries.first
@@ -54,7 +54,7 @@ module FakeFS
54
54
  files.each do |f|
55
55
  if RealFile.file?(f)
56
56
  FileUtils.mkdir_p(File.dirname(f))
57
- File.open(f, 'w') do |g|
57
+ File.open(f, File::WRITE_ONLY) do |g|
58
58
  g.print RealFile.open(f){|h| h.read }
59
59
  end
60
60
  elsif RealFile.directory?(f)
@@ -66,8 +66,8 @@ module FakeFS
66
66
  end
67
67
 
68
68
  def delete(path)
69
- if dir = FileSystem.find(path)
70
- dir.parent.delete(dir.name)
69
+ if node = FileSystem.find(path)
70
+ node.delete
71
71
  end
72
72
  end
73
73
 
@@ -106,13 +106,31 @@ module FakeFS
106
106
  return [] unless dir.respond_to? :[]
107
107
 
108
108
  pattern , *parts = parts
109
- matches = dir.reject {|k,v| /\A#{pattern.gsub('?','.').gsub('*', '.*')}\Z/ !~ k }.values
109
+ matches = case pattern
110
+ when '**'
111
+ case parts
112
+ when ['*'], []
113
+ parts = [] # end recursion
114
+ directories_under(dir).map do |d|
115
+ d.values.select{|f| f.is_a? FakeFile }
116
+ end.flatten.uniq
117
+ else
118
+ directories_under(dir)
119
+ end
120
+ else
121
+ dir.reject {|k,v| /\A#{pattern.gsub('?','.').gsub('*', '.*')}\Z/ !~ k }.values
122
+ end
110
123
 
111
124
  if parts.empty? # we're done recursing
112
125
  matches
113
126
  else
114
127
  matches.map{|entry| find_recurser(entry, parts) }
115
128
  end
116
- end
129
+ end
130
+
131
+ def directories_under(dir)
132
+ children = dir.values.select{|f| f.is_a? FakeDir}
133
+ ([dir] + children + children.map{|c| directories_under(c)}).flatten.uniq
134
+ end
117
135
  end
118
136
  end