fakefs 0.1.0 → 0.2.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.
- data/README.markdown +40 -7
- data/Rakefile +13 -20
- data/lib/fakefs/base.rb +2 -1
- data/lib/fakefs/dir.rb +93 -2
- data/lib/fakefs/fake/dir.rb +8 -0
- data/lib/fakefs/fake/file.rb +46 -3
- data/lib/fakefs/fake/symlink.rb +8 -2
- data/lib/fakefs/file.rb +163 -12
- data/lib/fakefs/file_system.rb +6 -6
- data/lib/fakefs/fileutils.rb +14 -0
- data/lib/fakefs/version.rb +1 -1
- data/test/fake/file_test.rb +88 -0
- data/test/fake/symlink_test.rb +11 -0
- data/test/fakefs_test.rb +581 -53
- data/test/file/stat_test.rb +70 -0
- data/test/safe_test.rb +9 -0
- metadata +9 -3
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
|
@@ -51,18 +51,51 @@ Don't Fake the FS Immediately
|
|
51
51
|
How is this different than MockFS?
|
52
52
|
----------------------------------
|
53
53
|
|
54
|
-
FakeFS provides a test suite and works with symlinks. It's also strictly a
|
54
|
+
FakeFS provides a test suite and works with symlinks. It's also strictly a
|
55
55
|
test-time dependency: your actual library does not need to use or know about
|
56
56
|
FakeFS.
|
57
57
|
|
58
58
|
|
59
59
|
Speed?
|
60
60
|
------
|
61
|
-
http://gist.github.com/156091
|
61
|
+
<http://gist.github.com/156091>
|
62
62
|
|
63
63
|
|
64
|
-
|
65
|
-
|
64
|
+
Installation
|
65
|
+
------------
|
66
66
|
|
67
|
-
|
68
|
-
|
67
|
+
### [Gemcutter](http://gemcutter.org/)
|
68
|
+
|
69
|
+
$ gem install fakefs
|
70
|
+
|
71
|
+
### [Rip](http://hellorip.com)
|
72
|
+
|
73
|
+
$ rip install git://github.com/defunkt/fakefs.git
|
74
|
+
|
75
|
+
|
76
|
+
Contributors
|
77
|
+
------------
|
78
|
+
|
79
|
+
* Chris Wanstrath
|
80
|
+
* David Reese
|
81
|
+
* Jeff Hodges
|
82
|
+
* Jon Yurek
|
83
|
+
* Matt Freels
|
84
|
+
* Myles Eftos
|
85
|
+
* Pat Nakajima
|
86
|
+
* Rob Sanheim
|
87
|
+
* Scott Taylor
|
88
|
+
* Tymon Tobolski
|
89
|
+
* msassak
|
90
|
+
|
91
|
+
|
92
|
+
Meta
|
93
|
+
----
|
94
|
+
|
95
|
+
* Code: `git clone git://github.com/defunkt/fakefs.git`
|
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
|
-
|
2
|
-
|
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,15 @@ 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
|
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."
|
40
33
|
end
|
data/lib/fakefs/base.rb
CHANGED
data/lib/fakefs/dir.rb
CHANGED
@@ -1,7 +1,55 @@
|
|
1
1
|
module FakeFS
|
2
2
|
class Dir
|
3
|
-
|
4
|
-
|
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 }
|
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
|
data/lib/fakefs/fake/dir.rb
CHANGED
data/lib/fakefs/fake/file.rb
CHANGED
@@ -1,16 +1,54 @@
|
|
1
1
|
module FakeFS
|
2
2
|
class FakeFile
|
3
|
-
attr_accessor :name, :parent
|
3
|
+
attr_accessor :name, :parent
|
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
|
+
end
|
4
23
|
|
5
24
|
def initialize(name = nil, parent = nil)
|
6
|
-
@name
|
25
|
+
@name = name
|
7
26
|
@parent = parent
|
8
|
-
@
|
27
|
+
@inode = Inode.new(self)
|
28
|
+
end
|
29
|
+
|
30
|
+
attr_accessor :inode
|
31
|
+
|
32
|
+
def content
|
33
|
+
@inode.content
|
34
|
+
end
|
35
|
+
|
36
|
+
def content=(str)
|
37
|
+
@inode.content = str
|
38
|
+
end
|
39
|
+
|
40
|
+
def links
|
41
|
+
@inode.links
|
42
|
+
end
|
43
|
+
|
44
|
+
def link(other_file)
|
45
|
+
@inode.link(other_file)
|
9
46
|
end
|
10
47
|
|
11
48
|
def clone(parent = nil)
|
12
49
|
clone = super()
|
13
50
|
clone.parent = parent if parent
|
51
|
+
clone.inode = inode.clone
|
14
52
|
clone
|
15
53
|
end
|
16
54
|
|
@@ -25,5 +63,10 @@ module FakeFS
|
|
25
63
|
def to_s
|
26
64
|
File.join(parent.to_s, name)
|
27
65
|
end
|
66
|
+
|
67
|
+
def delete
|
68
|
+
inode.unlink(self)
|
69
|
+
parent.delete(self)
|
70
|
+
end
|
28
71
|
end
|
29
72
|
end
|
data/lib/fakefs/fake/symlink.rb
CHANGED
@@ -15,12 +15,18 @@ module FakeFS
|
|
15
15
|
FileSystem.find(target)
|
16
16
|
end
|
17
17
|
|
18
|
-
def
|
19
|
-
|
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,35 @@ 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
|
+
READ_ONLY_MODES = [
|
17
|
+
READ_ONLY
|
18
|
+
]
|
19
|
+
|
20
|
+
WRITE_ONLY_MODES = [
|
21
|
+
WRITE_ONLY,
|
22
|
+
APPEND_WRITE_ONLY
|
23
|
+
]
|
24
|
+
|
25
|
+
TRUNCATION_MODES = [
|
26
|
+
WRITE_ONLY,
|
27
|
+
READ_WRITE_TRUNCATE
|
28
|
+
]
|
29
|
+
|
30
|
+
def self.extname(path)
|
31
|
+
RealFile.extname(path)
|
32
|
+
end
|
33
|
+
|
5
34
|
def self.join(*parts)
|
6
35
|
parts * PATH_SEPARATOR
|
7
36
|
end
|
@@ -14,6 +43,10 @@ module FakeFS
|
|
14
43
|
alias_method :exists?, :exist?
|
15
44
|
end
|
16
45
|
|
46
|
+
def self.size(path)
|
47
|
+
read(path).length
|
48
|
+
end
|
49
|
+
|
17
50
|
def self.const_missing(name)
|
18
51
|
RealFile.const_get(name)
|
19
52
|
end
|
@@ -61,11 +94,11 @@ module FakeFS
|
|
61
94
|
FileSystem.find(symlink.target).to_s
|
62
95
|
end
|
63
96
|
|
64
|
-
def self.open(path, mode=
|
97
|
+
def self.open(path, mode=READ_ONLY, perm = 0644)
|
65
98
|
if block_given?
|
66
|
-
yield new(path, mode)
|
99
|
+
yield new(path, mode, perm)
|
67
100
|
else
|
68
|
-
new(path, mode)
|
101
|
+
new(path, mode, perm)
|
69
102
|
end
|
70
103
|
end
|
71
104
|
|
@@ -81,13 +114,86 @@ module FakeFS
|
|
81
114
|
def self.readlines(path)
|
82
115
|
read(path).split("\n")
|
83
116
|
end
|
117
|
+
|
118
|
+
def self.link(source, dest)
|
119
|
+
if directory?(source)
|
120
|
+
raise Errno::EPERM, "Operation not permitted - #{source} or #{dest}"
|
121
|
+
end
|
122
|
+
|
123
|
+
if !exists?(source)
|
124
|
+
raise Errno::ENOENT, "No such file or directory - #{source} or #{dest}"
|
125
|
+
end
|
126
|
+
|
127
|
+
if exists?(dest)
|
128
|
+
raise Errno::EEXIST, "File exists - #{source} or #{dest}"
|
129
|
+
end
|
130
|
+
|
131
|
+
source = FileSystem.find(source)
|
132
|
+
dest = FileSystem.add(dest, source.entry.clone)
|
133
|
+
source.link(dest)
|
134
|
+
|
135
|
+
0
|
136
|
+
end
|
137
|
+
|
138
|
+
def self.delete(file_name, *additional_file_names)
|
139
|
+
if !exists?(file_name)
|
140
|
+
raise Errno::ENOENT, "No such file or directory - #{file_name}"
|
141
|
+
end
|
142
|
+
|
143
|
+
FileUtils.rm(file_name)
|
144
|
+
|
145
|
+
additional_file_names.each do |file_name|
|
146
|
+
FileUtils.rm(file_name)
|
147
|
+
end
|
148
|
+
|
149
|
+
additional_file_names.size + 1
|
150
|
+
end
|
151
|
+
|
152
|
+
class << self
|
153
|
+
alias_method :unlink, :delete
|
154
|
+
end
|
155
|
+
|
156
|
+
def self.symlink(source, dest)
|
157
|
+
FileUtils.ln_s(source, dest)
|
158
|
+
end
|
159
|
+
|
160
|
+
def self.stat(file)
|
161
|
+
File::Stat.new(file)
|
162
|
+
end
|
163
|
+
|
164
|
+
class Stat
|
165
|
+
def initialize(file)
|
166
|
+
if !File.exists?(file)
|
167
|
+
raise(Errno::ENOENT, "No such file or directory - #{file}")
|
168
|
+
end
|
169
|
+
|
170
|
+
@file = file
|
171
|
+
end
|
172
|
+
|
173
|
+
def symlink?
|
174
|
+
File.symlink?(@file)
|
175
|
+
end
|
176
|
+
|
177
|
+
def directory?
|
178
|
+
File.directory?(@file)
|
179
|
+
end
|
180
|
+
|
181
|
+
def nlink
|
182
|
+
FileSystem.find(@file).links.size
|
183
|
+
end
|
184
|
+
end
|
84
185
|
|
85
186
|
attr_reader :path
|
86
|
-
|
187
|
+
|
188
|
+
def initialize(path, mode = READ_ONLY, perm = nil)
|
87
189
|
@path = path
|
88
190
|
@mode = mode
|
89
191
|
@file = FileSystem.find(path)
|
90
192
|
@open = true
|
193
|
+
|
194
|
+
check_valid_mode
|
195
|
+
file_creation_mode? ? create_missing_file : check_file_existence!
|
196
|
+
truncate_file if truncation_mode?
|
91
197
|
end
|
92
198
|
|
93
199
|
def close
|
@@ -95,7 +201,9 @@ module FakeFS
|
|
95
201
|
end
|
96
202
|
|
97
203
|
def read
|
98
|
-
raise IOError
|
204
|
+
raise IOError, 'closed stream' unless @open
|
205
|
+
raise IOError, 'not opened for reading' if write_only?
|
206
|
+
|
99
207
|
@file.content
|
100
208
|
end
|
101
209
|
|
@@ -103,16 +211,15 @@ module FakeFS
|
|
103
211
|
@file
|
104
212
|
end
|
105
213
|
|
106
|
-
def puts(content)
|
107
|
-
|
214
|
+
def puts(*content)
|
215
|
+
content.flatten.each do |obj|
|
216
|
+
write(obj.to_s + "\n")
|
217
|
+
end
|
108
218
|
end
|
109
219
|
|
110
220
|
def write(content)
|
111
|
-
raise IOError
|
112
|
-
|
113
|
-
if !File.exists?(@path)
|
114
|
-
@file = FileSystem.add(path, FakeFile.new)
|
115
|
-
end
|
221
|
+
raise IOError, 'closed stream' unless @open
|
222
|
+
raise IOError, 'not open for writing' if read_only?
|
116
223
|
|
117
224
|
@file.content += content
|
118
225
|
end
|
@@ -120,5 +227,49 @@ module FakeFS
|
|
120
227
|
alias_method :<<, :write
|
121
228
|
|
122
229
|
def flush; self; end
|
230
|
+
|
231
|
+
private
|
232
|
+
|
233
|
+
def check_file_existence!
|
234
|
+
unless @file
|
235
|
+
raise Errno::ENOENT, "No such file or directory - #{@file}"
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
def check_valid_mode
|
240
|
+
if !mode_in?(MODES)
|
241
|
+
raise ArgumentError, "illegal access mode #{@mode}"
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
def read_only?
|
246
|
+
mode_in? READ_ONLY_MODES
|
247
|
+
end
|
248
|
+
|
249
|
+
def file_creation_mode?
|
250
|
+
mode_in? FILE_CREATION_MODES
|
251
|
+
end
|
252
|
+
|
253
|
+
def write_only?
|
254
|
+
mode_in? WRITE_ONLY_MODES
|
255
|
+
end
|
256
|
+
|
257
|
+
def truncation_mode?
|
258
|
+
mode_in? TRUNCATION_MODES
|
259
|
+
end
|
260
|
+
|
261
|
+
def mode_in?(list)
|
262
|
+
list.include?(@mode)
|
263
|
+
end
|
264
|
+
|
265
|
+
def create_missing_file
|
266
|
+
if !File.exists?(@path)
|
267
|
+
@file = FileSystem.add(path, FakeFile.new)
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
def truncate_file
|
272
|
+
@file.content = ""
|
273
|
+
end
|
123
274
|
end
|
124
275
|
end
|