better 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1 @@
1
+ Distributed under the same license as Ruby itself.
@@ -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)
@@ -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
@@ -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,3 @@
1
+ require 'better/tempfile'
2
+ Object.send(:remove_const, :Tempfile) if defined?(Tempfile)
3
+ ::Tempfile = Better::Tempfile
@@ -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,3 @@
1
+ $LOAD_PATH.unshift(File.expand_path(File.join(File.dirname(__FILE__), "..", "lib")))
2
+ output_file = ARGV.shift
3
+ STDOUT.reopen(output_file, "w")
@@ -0,0 +1,8 @@
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
+ puts path
7
+ file.close!
8
+ File.open(path, "w").close
@@ -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
+
@@ -0,0 +1,4 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), "example_helper"))
2
+ require 'better/tempfile'
3
+
4
+ puts Better::Tempfile.new('foo').path
@@ -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
+