better 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +1 -0
- data/README.rdoc +40 -0
- data/Rakefile +29 -0
- data/better.gemspec +23 -0
- data/lib/better/override/tempfile.rb +3 -0
- data/lib/better/tempfile.rb +391 -0
- data/test/example_helper.rb +3 -0
- data/test/tempfile_explicit_close_and_unlink_example.rb +8 -0
- data/test/tempfile_explicit_unlink_example.rb +12 -0
- data/test/tempfile_test.rb +283 -0
- data/test/tempfile_unlink_on_exit_example.rb +4 -0
- data/test/test_helper.rb +25 -0
- metadata +66 -0
data/LICENSE
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Distributed under the same license as Ruby itself.
|
data/README.rdoc
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
= Overview
|
2
|
+
|
3
|
+
Better is a collection of better replacements Ruby standard libraries. The versions shipped with Ruby have problems, which this library intends to fix. It is my wish that this code will one day find its way back to upstream Ruby.
|
4
|
+
|
5
|
+
* Github: http://github.com/FooBarWidget/better/tree/master
|
6
|
+
* API documentation: http://better.rubyforge.org/
|
7
|
+
|
8
|
+
== Usage
|
9
|
+
|
10
|
+
Install with:
|
11
|
+
|
12
|
+
gem install better
|
13
|
+
|
14
|
+
All of the libraries in Better are drop-in replacement and have the exact same API as the original, and work on both Ruby 1.8 and 1.9. For example, instead of
|
15
|
+
|
16
|
+
require 'tempfile'
|
17
|
+
Tempfile.new(...)
|
18
|
+
|
19
|
+
you just prefix the library filename with 'better/' and the class name with 'Better::':
|
20
|
+
|
21
|
+
require 'better/tempfile'
|
22
|
+
Better::Tempfile.new(...)
|
23
|
+
|
24
|
+
Or you can even override the standard version by requiring the library with the 'better/override/' prefix:
|
25
|
+
|
26
|
+
require 'better/override/tempfile'
|
27
|
+
Tempfile # => now refers to Better::Tempfile instead of ::Tempfile
|
28
|
+
|
29
|
+
This last feature should of course be used with care.
|
30
|
+
|
31
|
+
Please refer to the individual classes for more documentation.
|
32
|
+
|
33
|
+
== Contributing
|
34
|
+
|
35
|
+
The Github repository is located at http://github.com/FooBarWidget/better/tree/master.
|
36
|
+
Is there a Ruby standard library that you think can be improved? Just fork the repository and start hacking! It doesn't matter whether you want to fix a small bug, want to write unit tests or just want to improve documentation - anything is fine.
|
37
|
+
|
38
|
+
You can contact me at:
|
39
|
+
|
40
|
+
* Hongli Lai (hongli@phusion.nl)
|
data/Rakefile
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
begin
|
2
|
+
require 'rubygems'
|
3
|
+
require 'hanna/rdoctask'
|
4
|
+
rescue LoadError
|
5
|
+
STDERR.puts "*** Warning: you do not have the Hanna rdoc template installed. The rdoc will look better if you do. You should type:"
|
6
|
+
STDERR.puts " sudo gem sources -a http://gems.github.com"
|
7
|
+
STDERR.puts " sudo gem install mislav-hanna"
|
8
|
+
STDERR.puts
|
9
|
+
require 'rake/rdoctask'
|
10
|
+
end
|
11
|
+
require 'rake/testtask'
|
12
|
+
|
13
|
+
Rake::RDocTask.new do |rd|
|
14
|
+
rd.main = "README.rdoc"
|
15
|
+
rd.title = "Better: API documentation"
|
16
|
+
rd.rdoc_files.include("README.rdoc", "LICENSE", "lib/**/*.rb")
|
17
|
+
rd.rdoc_dir = "doc"
|
18
|
+
end
|
19
|
+
|
20
|
+
Rake::TestTask.new do |t|
|
21
|
+
t.libs << "test"
|
22
|
+
t.test_files = FileList['test/*_test.rb']
|
23
|
+
t.verbose = true
|
24
|
+
end
|
25
|
+
|
26
|
+
desc "Upload documentation to Rubyforge"
|
27
|
+
task :upload => :rdoc do
|
28
|
+
sh "scp -Cr doc/* rubyforge.org:/var/www/gforge-projects/better/"
|
29
|
+
end
|
data/better.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "better"
|
3
|
+
s.version = "1.0.0"
|
4
|
+
s.summary = "Collection of better replacements for Ruby standard libraries"
|
5
|
+
s.email = "hongli@phusion.nl"
|
6
|
+
s.homepage = "http://better.rubyforge.org/"
|
7
|
+
s.description = "Collection of better replacements for Ruby standard libraries."
|
8
|
+
s.has_rdoc = true
|
9
|
+
s.rubyforge_project = "better"
|
10
|
+
s.authors = ["Hongli Lai"]
|
11
|
+
|
12
|
+
s.files = [
|
13
|
+
"README.rdoc", "LICENSE", "better.gemspec", "Rakefile",
|
14
|
+
"lib/better/tempfile.rb",
|
15
|
+
"lib/better/override/tempfile.rb",
|
16
|
+
"test/test_helper.rb",
|
17
|
+
"test/example_helper.rb",
|
18
|
+
"test/tempfile_test.rb",
|
19
|
+
"test/tempfile_explicit_close_and_unlink_example.rb",
|
20
|
+
"test/tempfile_explicit_unlink_example.rb",
|
21
|
+
"test/tempfile_unlink_on_exit_example.rb"
|
22
|
+
]
|
23
|
+
end
|
@@ -0,0 +1,391 @@
|
|
1
|
+
#
|
2
|
+
# tempfile - manipulates temporary files
|
3
|
+
#
|
4
|
+
# $Id$
|
5
|
+
#
|
6
|
+
|
7
|
+
require 'delegate'
|
8
|
+
require 'tmpdir'
|
9
|
+
require 'thread'
|
10
|
+
|
11
|
+
module Better
|
12
|
+
|
13
|
+
# A utility class for managing temporary files. When you create a Tempfile
|
14
|
+
# object, it will create a temporary file with a unique filename. A Tempfile
|
15
|
+
# objects behaves just like a File object, and you can perform all the usual
|
16
|
+
# file operations on it: reading data, writing data, changing its permissions,
|
17
|
+
# etc. So although this class does not explicitly document all instance methods
|
18
|
+
# supported by File, you can in fact call any File instance method on a
|
19
|
+
# Tempfile object.
|
20
|
+
#
|
21
|
+
# == Comparison to Ruby's bundled version
|
22
|
+
#
|
23
|
+
# * Much better documentation.
|
24
|
+
# * Is unit tested.
|
25
|
+
# * Ruby 1.8's version can generate "weird" path names that can confuse certain
|
26
|
+
# command line tools such as Curl. Better::Tempfile is based on Ruby 1.9's
|
27
|
+
# version and generates saner filenames.
|
28
|
+
# * Ruby 1.8's version has a bug which makes unlink-before-close (as described
|
29
|
+
# below) unusable: it raises an an exception when #close is called if the
|
30
|
+
# tempfile was unlinked before.
|
31
|
+
# * Ruby 1.9.1's version closes the file when #unlink is called. This makes
|
32
|
+
# unlink-before-close unusable.
|
33
|
+
# * Ruby's bundled version deletes the temporary file in its finalizer, even
|
34
|
+
# when #unlink was called before. As a result it may potentially delete other
|
35
|
+
# Ruby processes' temp files when it's not supposed to.
|
36
|
+
#
|
37
|
+
# Better::Tempfile is based on Ruby 1.9.2's version (SVN 24594).
|
38
|
+
#
|
39
|
+
# == Synopsis
|
40
|
+
#
|
41
|
+
# require 'better/tempfile'
|
42
|
+
#
|
43
|
+
# file = Better::Tempfile.new('foo')
|
44
|
+
# file.path # => A unique filename in the OS's temp directory,
|
45
|
+
# # e.g.: "/tmp/foo.24722.0"
|
46
|
+
# # This filename contains 'foo' in its basename.
|
47
|
+
# file.write("hello world")
|
48
|
+
# file.rewind
|
49
|
+
# file.read # => "hello world"
|
50
|
+
# file.close
|
51
|
+
# file.unlink # deletes the temp file
|
52
|
+
#
|
53
|
+
# == Good practices
|
54
|
+
#
|
55
|
+
# === Explicit close
|
56
|
+
#
|
57
|
+
# When a Tempfile object is garbage collected, or when the Ruby interpreter
|
58
|
+
# exits, its associated temporary file is automatically deleted. This means
|
59
|
+
# that's it's unnecessary to explicitly delete a Tempfile after use, though
|
60
|
+
# it's good practice to do so: not explicitly deleting unused Tempfiles can
|
61
|
+
# potentially leave behind large amounts of tempfiles on the filesystem
|
62
|
+
# until they're garbage collected. The existance of these temp files can make
|
63
|
+
# it harder to determine a new Tempfile filename.
|
64
|
+
#
|
65
|
+
# Therefore, one should always call #unlink or close in an ensure block, like
|
66
|
+
# this:
|
67
|
+
#
|
68
|
+
# file = Better::Tempfile.new('foo)
|
69
|
+
# begin
|
70
|
+
# ...do something with file...
|
71
|
+
# ensure
|
72
|
+
# file.close
|
73
|
+
# file.unlink # deletes the temp file
|
74
|
+
# end
|
75
|
+
#
|
76
|
+
# === Unlink after creation
|
77
|
+
#
|
78
|
+
# On POSIX systems, it's possible to unlink a file right after creating it,
|
79
|
+
# and before closing it. This removes the filesystem entry without closing
|
80
|
+
# the file handle, so it ensures that only the processes that already had
|
81
|
+
# the file handle open can access the file's contents. It's strongly
|
82
|
+
# recommended that you do this if you do not want any other processes to
|
83
|
+
# be able to read from or write to the Tempfile, and you do not need to
|
84
|
+
# know the Tempfile's filename either.
|
85
|
+
#
|
86
|
+
# For example, a practical use case for unlink-after-creation would be this:
|
87
|
+
# you need a large byte buffer that's too large to comfortably fit in RAM,
|
88
|
+
# e.g. when you're writing a web server and you want to buffer the client's
|
89
|
+
# file upload data.
|
90
|
+
#
|
91
|
+
# Please refer to #unlink for more information and a code example.
|
92
|
+
#
|
93
|
+
# == Minor notes
|
94
|
+
#
|
95
|
+
# Tempfile's filename picking method is both thread-safe and inter-process-safe:
|
96
|
+
# it guarantees that no other threads or processes will pick the same filename.
|
97
|
+
#
|
98
|
+
# Tempfile itself however may not be entirely thread-safe. If you access the
|
99
|
+
# same Tempfile object from multiple threads then you should protect it with a
|
100
|
+
# mutex.
|
101
|
+
class Tempfile < DelegateClass(File)
|
102
|
+
MAX_TRIES = 10 # :nodoc:
|
103
|
+
@@live_tempfiles = []
|
104
|
+
@@lock = Mutex.new
|
105
|
+
|
106
|
+
class CreationError < StandardError # :nodoc:
|
107
|
+
end
|
108
|
+
|
109
|
+
class << self
|
110
|
+
# Creates a new Tempfile.
|
111
|
+
#
|
112
|
+
# If no block is given, this is a synonym for Tempfile.new.
|
113
|
+
#
|
114
|
+
# If a block is given, then a Tempfile object will be constructed,
|
115
|
+
# and the block is run with said object as argument. The Tempfile
|
116
|
+
# oject will be automatically closed after the block terminates.
|
117
|
+
# The call returns the value of the block.
|
118
|
+
#
|
119
|
+
# In any case, all arguments (+*args+) will be passed to Tempfile.new.
|
120
|
+
#
|
121
|
+
# Better::Tempfile.open('foo', '/home/temp') do |f|
|
122
|
+
# ... do something with f ...
|
123
|
+
# end
|
124
|
+
#
|
125
|
+
# # Equivalent:
|
126
|
+
# f = Better::Tempfile.open('foo', '/home/temp')
|
127
|
+
# begin
|
128
|
+
# ... do something with f ...
|
129
|
+
# ensure
|
130
|
+
# f.close
|
131
|
+
# end
|
132
|
+
def open(*args)
|
133
|
+
tempfile = new(*args)
|
134
|
+
|
135
|
+
if block_given?
|
136
|
+
begin
|
137
|
+
yield(tempfile)
|
138
|
+
ensure
|
139
|
+
tempfile.close
|
140
|
+
end
|
141
|
+
else
|
142
|
+
tempfile
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def create_finalizer_callback(info) # :nodoc:
|
147
|
+
original_pid = $$
|
148
|
+
Proc.new do
|
149
|
+
# If we forked, then don't cleanup the temp files created by
|
150
|
+
# the parent process.
|
151
|
+
if original_pid == $$
|
152
|
+
path, tmpfile, live_tempfiles = *info
|
153
|
+
tmpfile.close if tmpfile
|
154
|
+
File.unlink(path) if path && File.exist?(path)
|
155
|
+
live_tempfiles.delete(path) if live_tempfiles
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def make_directory(dir) # :nodoc:
|
161
|
+
Dir.mkdir(dir)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
# call-seq:
|
166
|
+
# new(basename, [tmpdir = Dir.tmpdir], [options])
|
167
|
+
#
|
168
|
+
# Creates a temporary file with permissions 0600 (= only readable and
|
169
|
+
# writable by the owner) and opens it with mode "w+".
|
170
|
+
#
|
171
|
+
# The +basename+ parameter is used to determine the name of the
|
172
|
+
# temporary file. You can either pass a String or an Array with
|
173
|
+
# 2 String elements. In the former form, the temporary file's base
|
174
|
+
# name will begin with the given string. In the latter form,
|
175
|
+
# the temporary file's base name will begin with the array's first
|
176
|
+
# element, and end with the second element. For example:
|
177
|
+
#
|
178
|
+
# file = Better::Tempfile.new('hello')
|
179
|
+
# file.path # => something like: "/tmp/foo2843-8392-92849382--0"
|
180
|
+
#
|
181
|
+
# # Use the Array form to enforce an extension in the filename:
|
182
|
+
# file = Better::Tempfile.new(['hello', '.jpg'])
|
183
|
+
# file.path # => something like: "/tmp/foo2843-8392-92849382--0.jpg"
|
184
|
+
#
|
185
|
+
# The temporary file will be placed in the directory as specified
|
186
|
+
# by the +tmpdir+ parameter. By default, this is +Dir.tmpdir+ (see
|
187
|
+
# 'tmpdir.rb' in the Ruby standard library.)
|
188
|
+
# When $SAFE > 0 and the given +tmpdir+ is tainted, it uses
|
189
|
+
# '/tmp' as the temporary directory. Please note that ENV values
|
190
|
+
# are tainted by default, and +Dir.tmpdir+'s return value might
|
191
|
+
# come from environment variables (e.g. <tt>$TMPDIR</tt>).
|
192
|
+
#
|
193
|
+
# file = Better::Tempfile.new('hello', '/home/aisaka')
|
194
|
+
# file.path # => something like: "/home/aisaka/foo2843-8392-92849382--0"
|
195
|
+
#
|
196
|
+
# You can also pass an options hash. Under the hood, Better::Tempfile creates
|
197
|
+
# the temporary file using +File.open+. These options will be passed to
|
198
|
+
# +File.open+. This is mostly useful on Ruby 1.9 for specifying encoding
|
199
|
+
# options, e.g.:
|
200
|
+
#
|
201
|
+
# Better::Tempfile.new('hello', '/home/aisaka', :encoding => 'ascii-8bit')
|
202
|
+
#
|
203
|
+
# # You can also omit the 'tmpdir' parameter:
|
204
|
+
# Better::Tempfile.new('hello', :encoding => 'ascii-8bit')
|
205
|
+
#
|
206
|
+
# === Exceptions
|
207
|
+
#
|
208
|
+
# Under rare circumstances, this constructor can raise an instance of
|
209
|
+
# Better::Tempfile::CreationError. This could happen if a large number
|
210
|
+
# of threads or processes are simultaneously trying to create temp files
|
211
|
+
# and stepping on each others' toes. If Better::Tempfile.new cannot find
|
212
|
+
# a unique filename within a limited number of tries, then it will raise
|
213
|
+
# this exception.
|
214
|
+
def initialize(basename, *rest)
|
215
|
+
# I wish keyword argument settled soon.
|
216
|
+
if rest.last.respond_to?(:to_hash)
|
217
|
+
opts = rest.last.to_hash
|
218
|
+
rest.pop
|
219
|
+
else
|
220
|
+
opts = nil
|
221
|
+
end
|
222
|
+
tmpdir = rest[0] || Dir::tmpdir
|
223
|
+
if $SAFE > 0 && tmpdir.tainted?
|
224
|
+
tmpdir = '/tmp'
|
225
|
+
end
|
226
|
+
|
227
|
+
lock = tmpname = nil
|
228
|
+
n = failure = 0
|
229
|
+
@@lock.synchronize do
|
230
|
+
begin
|
231
|
+
begin
|
232
|
+
tmpname = File.join(tmpdir, make_tmpname(basename, n))
|
233
|
+
lock = tmpname + '.lock'
|
234
|
+
n += 1
|
235
|
+
end while @@live_tempfiles.include?(tmpname) ||
|
236
|
+
File.exist?(lock) ||
|
237
|
+
File.exist?(tmpname)
|
238
|
+
self.class.make_directory(lock)
|
239
|
+
rescue SystemCallError
|
240
|
+
failure += 1
|
241
|
+
retry if failure < MAX_TRIES
|
242
|
+
raise CreationError, ("cannot generate tempfile `%s'" % tmpname)
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
@finalizer_info = [tmpname]
|
247
|
+
@finalizer_callback = self.class.create_finalizer_callback(@finalizer_info)
|
248
|
+
ObjectSpace.define_finalizer(self, @finalizer_callback)
|
249
|
+
|
250
|
+
if opts.nil?
|
251
|
+
opts = []
|
252
|
+
else
|
253
|
+
opts = [opts]
|
254
|
+
end
|
255
|
+
@tmpfile = File.open(tmpname, File::RDWR | File::CREAT | File::EXCL, 0600, *opts)
|
256
|
+
@tmpname = tmpname
|
257
|
+
@@live_tempfiles << tmpname
|
258
|
+
@finalizer_info[1] = @tmpfile
|
259
|
+
@finalizer_info[2] = @@live_tempfiles
|
260
|
+
|
261
|
+
super(@tmpfile)
|
262
|
+
|
263
|
+
# Now we have all the File/IO methods defined, you must not
|
264
|
+
# carelessly put bare puts(), etc. after this.
|
265
|
+
|
266
|
+
Dir.rmdir(lock)
|
267
|
+
end
|
268
|
+
|
269
|
+
# Opens or reopens the file with mode "r+".
|
270
|
+
def open
|
271
|
+
@tmpfile.close if @tmpfile
|
272
|
+
@tmpfile = File.open(@tmpname, 'r+')
|
273
|
+
@finalizer_info[1] = @tmpfile
|
274
|
+
__setobj__(@tmpfile)
|
275
|
+
end
|
276
|
+
|
277
|
+
# Closes the file. If +unlink_now+ is true, then the file will be unlinked
|
278
|
+
# (deleted) after closing. Of course, you can choose to later call #unlink
|
279
|
+
# if you do not unlink it now.
|
280
|
+
def close(unlink_now = false)
|
281
|
+
if unlink_now
|
282
|
+
close!
|
283
|
+
else
|
284
|
+
_close
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
# Closes and unlinks (deletes) the file. Has the same effect as called
|
289
|
+
# <tt>close(true)</tt>.
|
290
|
+
def close!
|
291
|
+
_close
|
292
|
+
unlink if !unlinked?
|
293
|
+
end
|
294
|
+
|
295
|
+
# Unlinks (deletes) the file from the filesystem. One should always unlink
|
296
|
+
# the file after using it, as is explained in the "Explicit close" good
|
297
|
+
# practice section in the Tempfile overview:
|
298
|
+
#
|
299
|
+
# file = Better::Tempfile.new('foo)
|
300
|
+
# begin
|
301
|
+
# ...do something with file...
|
302
|
+
# ensure
|
303
|
+
# file.close
|
304
|
+
# file.unlink # deletes the temp file
|
305
|
+
# end
|
306
|
+
#
|
307
|
+
# === Unlink-before-close
|
308
|
+
#
|
309
|
+
# On POSIX systems it's possible to unlink a file before closing it. This
|
310
|
+
# practice is explained in detail in the Tempfile overview (section
|
311
|
+
# "Unlink after creation"); please refer there for more information.
|
312
|
+
#
|
313
|
+
# However, unlink-before-close may not be supported on non-POSIX operating
|
314
|
+
# systems. Microsoft Windows is the most notable case: unlinking a non-closed
|
315
|
+
# file will result in an error, which this method will silently ignore. If
|
316
|
+
# you want to practice unlink-before-close whenever possible, then you should
|
317
|
+
# write code like this:
|
318
|
+
#
|
319
|
+
# file = Better::Tempfile.new('foo')
|
320
|
+
# file.unlink # On Windows this silently fails.
|
321
|
+
# begin
|
322
|
+
# ... do something with file ...
|
323
|
+
# ensure
|
324
|
+
# file.close! # Closes the file handle. If the file wasn't unlinked
|
325
|
+
# # because #unlink failed, then this method will attempt
|
326
|
+
# # to do so again.
|
327
|
+
# end
|
328
|
+
def unlink
|
329
|
+
begin
|
330
|
+
if File.exist?(@tmpname) # keep this order for thread safeness
|
331
|
+
unlink_file(@tmpname)
|
332
|
+
end
|
333
|
+
@@live_tempfiles.delete(@tmpname)
|
334
|
+
@finalizer_info = @tmpname = nil
|
335
|
+
ObjectSpace.undefine_finalizer(self)
|
336
|
+
rescue Errno::EACCES
|
337
|
+
# may not be able to unlink on Windows; just ignore
|
338
|
+
end
|
339
|
+
end
|
340
|
+
alias delete unlink
|
341
|
+
|
342
|
+
# Returns whether #unlink has been called on this Tempfile, and whether it
|
343
|
+
# succeeded.
|
344
|
+
def unlinked?
|
345
|
+
@tmpname.nil?
|
346
|
+
end
|
347
|
+
|
348
|
+
# Returns the full path name of the temporary file. This will be nil if
|
349
|
+
# #unlink has been called.
|
350
|
+
def path
|
351
|
+
@tmpname
|
352
|
+
end
|
353
|
+
|
354
|
+
# Returns the size of the temporary file. As a side effect, the IO
|
355
|
+
# buffer is flushed before determining the size.
|
356
|
+
def size
|
357
|
+
if @tmpfile
|
358
|
+
@tmpfile.flush
|
359
|
+
@tmpfile.stat.size
|
360
|
+
else
|
361
|
+
0
|
362
|
+
end
|
363
|
+
end
|
364
|
+
alias length size
|
365
|
+
|
366
|
+
protected
|
367
|
+
def _close # :nodoc:
|
368
|
+
@tmpfile.close if @tmpfile
|
369
|
+
@tmpfile = nil
|
370
|
+
@finalizer_info[1] = nil if @finalizer_info
|
371
|
+
end
|
372
|
+
|
373
|
+
private
|
374
|
+
def unlink_file(filename)
|
375
|
+
File.unlink(filename)
|
376
|
+
end
|
377
|
+
|
378
|
+
def make_tmpname(basename, n)
|
379
|
+
case basename
|
380
|
+
when Array
|
381
|
+
prefix, suffix = *basename
|
382
|
+
else
|
383
|
+
prefix, suffix = basename, ''
|
384
|
+
end
|
385
|
+
|
386
|
+
t = Time.now.strftime("%Y%m%d")
|
387
|
+
path = "#{prefix}#{t}-#{$$}-#{rand(0x100000000).to_s(36)}-#{n}#{suffix}"
|
388
|
+
end
|
389
|
+
end
|
390
|
+
|
391
|
+
end # module Better
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "example_helper"))
|
2
|
+
require 'better/tempfile'
|
3
|
+
|
4
|
+
file = Better::Tempfile.new('foo')
|
5
|
+
path = file.path
|
6
|
+
file.unlink
|
7
|
+
if file.unlinked?
|
8
|
+
puts path
|
9
|
+
File.open(path, "w").close
|
10
|
+
else
|
11
|
+
file.close!
|
12
|
+
end
|
@@ -0,0 +1,283 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "test_helper"))
|
2
|
+
require 'better/tempfile'
|
3
|
+
require 'thread'
|
4
|
+
|
5
|
+
class TempfileTest < Test::Unit::TestCase
|
6
|
+
include Better
|
7
|
+
|
8
|
+
def teardown
|
9
|
+
if @tempfile
|
10
|
+
@tempfile.close if !@tempfile.closed?
|
11
|
+
@tempfile.unlink if !@tempfile.unlinked?
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_basic
|
16
|
+
@tempfile = Tempfile.new("foo")
|
17
|
+
path = @tempfile.path
|
18
|
+
@tempfile.write("hello world")
|
19
|
+
@tempfile.close
|
20
|
+
assert_equal "hello world", File.read(path)
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_saves_in_dir_tmpdir_by_default
|
24
|
+
@tempfile = Tempfile.new("foo")
|
25
|
+
assert_equal Dir.tmpdir, File.dirname(@tempfile.path)
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_saves_in_given_directory
|
29
|
+
subdir = File.join(Dir.tmpdir, "tempfile-test-#{rand}")
|
30
|
+
Dir.mkdir(subdir)
|
31
|
+
begin
|
32
|
+
tempfile = Tempfile.new("foo", subdir)
|
33
|
+
tempfile.close
|
34
|
+
begin
|
35
|
+
assert_equal subdir, File.dirname(tempfile.path)
|
36
|
+
ensure
|
37
|
+
tempfile.unlink
|
38
|
+
end
|
39
|
+
ensure
|
40
|
+
Dir.rmdir(subdir)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_basename
|
45
|
+
@tempfile = Tempfile.new("foo")
|
46
|
+
assert_match /^foo/, File.basename(@tempfile.path)
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_basename_with_suffix
|
50
|
+
@tempfile = Tempfile.new(["foo", ".txt"])
|
51
|
+
assert_match /^foo/, File.basename(@tempfile.path)
|
52
|
+
assert_match /\.txt$/, File.basename(@tempfile.path)
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_raises_creation_error_if_max_tries_surpassed
|
56
|
+
Tempfile.expects(:make_directory).times(Tempfile::MAX_TRIES).raises(Errno::EEXIST)
|
57
|
+
assert_raises(Tempfile::CreationError) do
|
58
|
+
@tempfile = Tempfile.new('foo')
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_unlink_and_unlink_p
|
63
|
+
@tempfile = Tempfile.new("foo")
|
64
|
+
path = @tempfile.path
|
65
|
+
assert !@tempfile.unlinked?
|
66
|
+
|
67
|
+
@tempfile.close
|
68
|
+
assert !@tempfile.unlinked?
|
69
|
+
assert File.exist?(path)
|
70
|
+
|
71
|
+
@tempfile.unlink
|
72
|
+
assert @tempfile.unlinked?
|
73
|
+
assert !File.exist?(path)
|
74
|
+
|
75
|
+
@tempfile = nil
|
76
|
+
end
|
77
|
+
|
78
|
+
def test_unlink_makes_path_nil
|
79
|
+
@tempfile = Tempfile.new("foo")
|
80
|
+
@tempfile.close
|
81
|
+
@tempfile.unlink
|
82
|
+
assert_nil @tempfile.path
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_unlink_silently_fails_on_windows
|
86
|
+
tempfile = Tempfile.new("foo")
|
87
|
+
path = tempfile.path
|
88
|
+
begin
|
89
|
+
tempfile.expects(:unlink_file).with(path).raises(Errno::EACCES)
|
90
|
+
assert_nothing_raised do
|
91
|
+
tempfile.unlink
|
92
|
+
end
|
93
|
+
assert !tempfile.unlinked?
|
94
|
+
ensure
|
95
|
+
tempfile.close
|
96
|
+
File.unlink(path)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def test_unlink_before_close_works_on_posix_systems
|
101
|
+
tempfile = Tempfile.new("foo")
|
102
|
+
begin
|
103
|
+
path = tempfile.path
|
104
|
+
tempfile.unlink
|
105
|
+
if tempfile.unlinked?
|
106
|
+
assert !File.exist?(path)
|
107
|
+
tempfile.write("hello ")
|
108
|
+
tempfile.write("world\n")
|
109
|
+
tempfile.rewind
|
110
|
+
assert_equal "hello world\n", tempfile.read
|
111
|
+
end
|
112
|
+
ensure
|
113
|
+
tempfile.close
|
114
|
+
tempfile.unlink if !tempfile.unlinked?
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def test_close_and_close_p
|
119
|
+
@tempfile = Tempfile.new("foo")
|
120
|
+
assert !@tempfile.closed?
|
121
|
+
@tempfile.close
|
122
|
+
assert @tempfile.closed?
|
123
|
+
end
|
124
|
+
|
125
|
+
def test_close_with_unlink_now_true_works
|
126
|
+
@tempfile = Tempfile.new("foo")
|
127
|
+
path = @tempfile.path
|
128
|
+
@tempfile.close(true)
|
129
|
+
assert @tempfile.closed?
|
130
|
+
assert @tempfile.unlinked?
|
131
|
+
assert_nil @tempfile.path
|
132
|
+
assert !File.exist?(path)
|
133
|
+
end
|
134
|
+
|
135
|
+
def test_close_with_unlink_now_true_does_not_unlink_if_already_unlinked
|
136
|
+
@tempfile = Tempfile.new("foo")
|
137
|
+
path = @tempfile.path
|
138
|
+
@tempfile.unlink
|
139
|
+
File.open(path, "w").close
|
140
|
+
begin
|
141
|
+
@tempfile.close(true)
|
142
|
+
assert File.exist?(path)
|
143
|
+
ensure
|
144
|
+
File.unlink(path) rescue nil
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def test_close_bang_works
|
149
|
+
@tempfile = Tempfile.new("foo")
|
150
|
+
path = @tempfile.path
|
151
|
+
@tempfile.close!
|
152
|
+
assert @tempfile.closed?
|
153
|
+
assert @tempfile.unlinked?
|
154
|
+
assert_nil @tempfile.path
|
155
|
+
assert !File.exist?(path)
|
156
|
+
end
|
157
|
+
|
158
|
+
def test_close_bang_does_not_unlink_if_already_unlinked
|
159
|
+
@tempfile = Tempfile.new("foo")
|
160
|
+
path = @tempfile.path
|
161
|
+
@tempfile.unlink
|
162
|
+
File.open(path, "w").close
|
163
|
+
begin
|
164
|
+
@tempfile.close!
|
165
|
+
assert File.exist?(path)
|
166
|
+
ensure
|
167
|
+
File.unlink(path) rescue nil
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def test_finalizer_does_not_unlink_if_already_unlinked
|
172
|
+
filename = run_script("tempfile_explicit_close_and_unlink_example.rb").strip
|
173
|
+
assert File.exist?(filename)
|
174
|
+
File.unlink(filename)
|
175
|
+
|
176
|
+
filename = run_script("tempfile_explicit_unlink_example.rb").strip
|
177
|
+
if !filename.empty?
|
178
|
+
# POSIX unlink semantics supported, continue with test
|
179
|
+
assert File.exist?(filename)
|
180
|
+
File.unlink(filename)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def test_close_does_not_make_path_nil
|
185
|
+
@tempfile = Tempfile.new("foo")
|
186
|
+
@tempfile.close
|
187
|
+
assert_not_nil @tempfile.path
|
188
|
+
end
|
189
|
+
|
190
|
+
def test_close_flushes_buffer
|
191
|
+
@tempfile = Tempfile.new("foo")
|
192
|
+
@tempfile.write("hello")
|
193
|
+
@tempfile.close
|
194
|
+
assert 5, File.size(@tempfile.path)
|
195
|
+
end
|
196
|
+
|
197
|
+
def test_tempfile_is_unlinked_when_ruby_exits
|
198
|
+
filename = run_script("tempfile_unlink_on_exit_example.rb").strip
|
199
|
+
assert !File.exist?(filename)
|
200
|
+
end
|
201
|
+
|
202
|
+
def test_size_flushes_buffer_before_determining_file_size
|
203
|
+
@tempfile = Tempfile.new("foo")
|
204
|
+
@tempfile.write("hello")
|
205
|
+
assert 0, File.size(@tempfile.path)
|
206
|
+
assert 5, @tempfile.size
|
207
|
+
assert 5, File.size(@tempfile.path)
|
208
|
+
end
|
209
|
+
|
210
|
+
def test_size_works_if_file_is_closed
|
211
|
+
@tempfile = Tempfile.new("foo")
|
212
|
+
@tempfile.write("hello")
|
213
|
+
@tempfile.close
|
214
|
+
assert 5, @tempfile.size
|
215
|
+
end
|
216
|
+
|
217
|
+
def test_concurrency
|
218
|
+
threads = []
|
219
|
+
tempfiles = []
|
220
|
+
lock = Mutex.new
|
221
|
+
cond = ConditionVariable.new
|
222
|
+
start = false
|
223
|
+
|
224
|
+
4.times do
|
225
|
+
threads << Thread.new do
|
226
|
+
lock.synchronize do
|
227
|
+
while !start
|
228
|
+
cond.wait(lock)
|
229
|
+
end
|
230
|
+
end
|
231
|
+
result = []
|
232
|
+
30.times do
|
233
|
+
result << Tempfile.new('foo')
|
234
|
+
end
|
235
|
+
Thread.current[:result] = result
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
lock.synchronize do
|
240
|
+
start = true
|
241
|
+
cond.broadcast
|
242
|
+
end
|
243
|
+
threads.each do |thread|
|
244
|
+
thread.join
|
245
|
+
tempfiles |= thread[:result]
|
246
|
+
end
|
247
|
+
filenames = tempfiles.map { |f| f.path }
|
248
|
+
begin
|
249
|
+
assert_equal filenames.size, filenames.uniq.size
|
250
|
+
ensure
|
251
|
+
tempfiles.each do |tempfile|
|
252
|
+
tempfile.close!
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
if defined?(Encoding)
|
258
|
+
def test_tempfile_encoding_nooption
|
259
|
+
default_external = Encoding.default_external
|
260
|
+
t = Tempfile.new("TEST")
|
261
|
+
t.write("\xE6\x9D\xBE\xE6\xB1\x9F")
|
262
|
+
t.rewind
|
263
|
+
assert_equal(default_external, t.read.encoding)
|
264
|
+
end
|
265
|
+
|
266
|
+
def test_tempfile_encoding_ascii8bit
|
267
|
+
default_external = Encoding.default_external
|
268
|
+
t = Tempfile.new("TEST", :encoding => "ascii-8bit")
|
269
|
+
t.write("\xE6\x9D\xBE\xE6\xB1\x9F")
|
270
|
+
t.rewind
|
271
|
+
assert_equal(Encoding::ASCII_8BIT, t.read.encoding)
|
272
|
+
end
|
273
|
+
|
274
|
+
def test_tempfile_encoding_ascii8bit2
|
275
|
+
default_external = Encoding.default_external
|
276
|
+
t = Tempfile.new("TEST", Dir::tmpdir, :encoding => "ascii-8bit")
|
277
|
+
t.write("\xE6\x9D\xBE\xE6\xB1\x9F")
|
278
|
+
t.rewind
|
279
|
+
assert_equal(Encoding::ASCII_8BIT, t.read.encoding)
|
280
|
+
end
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.expand_path(File.join(File.dirname(__FILE__), "..", "lib")))
|
2
|
+
require 'rubygems'
|
3
|
+
require 'test/unit'
|
4
|
+
require 'rbconfig'
|
5
|
+
require 'better/tempfile'
|
6
|
+
require 'mocha' # must be included last, otherwise it won't work!
|
7
|
+
|
8
|
+
def run_script(script, *args)
|
9
|
+
output = Better::Tempfile.new('output')
|
10
|
+
begin
|
11
|
+
output.close
|
12
|
+
|
13
|
+
ruby = File.join(Config::CONFIG['bindir'], Config::CONFIG['ruby_install_name']) + Config::CONFIG['EXEEXT']
|
14
|
+
command = [ruby, File.join(File.dirname(__FILE__), script), output.path, *args]
|
15
|
+
|
16
|
+
if system(*command)
|
17
|
+
File.read(output.path)
|
18
|
+
else
|
19
|
+
raise "Command failed: #{command.join(' ')}"
|
20
|
+
end
|
21
|
+
ensure
|
22
|
+
output.close if !output.closed?
|
23
|
+
output.unlink
|
24
|
+
end
|
25
|
+
end
|
metadata
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: better
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Hongli Lai
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-04-28 00:00:00 +02:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: Collection of better replacements for Ruby standard libraries.
|
17
|
+
email: hongli@phusion.nl
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files: []
|
23
|
+
|
24
|
+
files:
|
25
|
+
- README.rdoc
|
26
|
+
- LICENSE
|
27
|
+
- better.gemspec
|
28
|
+
- Rakefile
|
29
|
+
- lib/better/tempfile.rb
|
30
|
+
- lib/better/override/tempfile.rb
|
31
|
+
- test/test_helper.rb
|
32
|
+
- test/example_helper.rb
|
33
|
+
- test/tempfile_test.rb
|
34
|
+
- test/tempfile_explicit_close_and_unlink_example.rb
|
35
|
+
- test/tempfile_explicit_unlink_example.rb
|
36
|
+
- test/tempfile_unlink_on_exit_example.rb
|
37
|
+
has_rdoc: true
|
38
|
+
homepage: http://better.rubyforge.org/
|
39
|
+
licenses: []
|
40
|
+
|
41
|
+
post_install_message:
|
42
|
+
rdoc_options: []
|
43
|
+
|
44
|
+
require_paths:
|
45
|
+
- lib
|
46
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
47
|
+
requirements:
|
48
|
+
- - ">="
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: "0"
|
51
|
+
version:
|
52
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: "0"
|
57
|
+
version:
|
58
|
+
requirements: []
|
59
|
+
|
60
|
+
rubyforge_project: better
|
61
|
+
rubygems_version: 1.3.5
|
62
|
+
signing_key:
|
63
|
+
specification_version: 3
|
64
|
+
summary: Collection of better replacements for Ruby standard libraries
|
65
|
+
test_files: []
|
66
|
+
|