mischacks 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
data/COPYING ADDED
@@ -0,0 +1,13 @@
1
+ Copyright © 2009 Johan Kiviniemi
2
+
3
+ Permission to use, copy, modify, and/or distribute this software for any
4
+ purpose with or without fee is hereby granted, provided that the above
5
+ copyright notice and this permission notice appear in all copies.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
data/History.txt ADDED
@@ -0,0 +1,25 @@
1
+ === UNRELEASED / UNRELEASED
2
+
3
+ * Update README.
4
+ * Test best_datasync more thoroughly.
5
+
6
+ === 0.0.5 / 2009-06-28
7
+
8
+ * Implement IO#best_datasync, have overwrite use it.
9
+
10
+ === 0.0.4 / 2009-06-28
11
+
12
+ * Implement overwrite, a method to safely replace a file.
13
+ * Update README.
14
+ * Move exception printing from sh to catching_exit.
15
+
16
+ === 0.0.3 / 2009-06-28
17
+
18
+ * Rename checking_exit_status as fork_and_check to make it clear it forks.
19
+ * Add catching_exit, which is now used by the do_and_exit methods.
20
+ * Pass a fallthrough exit status to do_and_exit.
21
+
22
+ === 0.0.2 / 2009-06-28
23
+
24
+ * Initial release
25
+
data/Manifest.txt ADDED
@@ -0,0 +1,9 @@
1
+ COPYING
2
+ History.txt
3
+ Manifest.txt
4
+ README.txt
5
+ Rakefile
6
+ lib/mischacks.rb
7
+ mischacks.gemspec
8
+ spec/mischacks_spec.rb
9
+ spec/spec_helper.rb
data/README.txt ADDED
@@ -0,0 +1,119 @@
1
+ = MiscHacks
2
+
3
+ * http://johan.kiviniemi.name/software/mischacks/
4
+
5
+ == DESCRIPTION:
6
+
7
+ Miscellaneous methods that may or may not be useful.
8
+
9
+ sh:: Safely pass untrusted parameters to sh scripts.
10
+
11
+ fork_and_check:: Run a block in a forked process and raise an exception if the
12
+ process returns a non-zero value.
13
+
14
+ do_and_exit, do_and_exit!:: Run a block. If the block does not run exit!, a
15
+ successful exec or equivalent, run exit(1) or exit!(1) ourselves. Useful to
16
+ make sure a forked block either runs a successful exec or dies.
17
+
18
+ Any exceptions from the block are printed to standard error.
19
+
20
+ overwrite:: Safely replace a file. Writes to a temporary file and then moves it
21
+ over the old file.
22
+
23
+ tempname_for:: Generates an unique temporary path based on a filename. The
24
+ generated filename resides in the same directory as the original one.
25
+
26
+ try_n_times:: Retries a block of code until it succeeds or a maximum number of
27
+ attempts (default 10) is exceeded.
28
+
29
+ Exception#to_formatted_string:: Returns a string that looks like how Ruby would
30
+ dump an uncaught exception.
31
+
32
+ IO#best_datasync:: Tries fdatasync, falling back to fsync, falling back to
33
+ flush.
34
+
35
+ == FEATURES/PROBLEMS:
36
+
37
+ The sh method is only safe if your sh script is safe. If unsure, add double
38
+ quotation marks around all variable references ("$1", "$foo", "$@"), and never,
39
+ ever use an untrusted variable as a command.
40
+
41
+ == SYNOPSIS:
42
+
43
+ # sh
44
+
45
+ MiscHacks.sh %q{
46
+ diff -u "$1" "$2" | tr a-z A-Z >"$output"
47
+ }, '/dev/null', '/etc/motd', :output => 'foo'
48
+
49
+ unsafe_str = %q{" 'foo' $(bar) `baz` "}
50
+ MiscHacks.sh 'printf "%s\n" "$1"', unsafe_str
51
+
52
+ # fork_and_check
53
+
54
+ # These examples raise MiscHacks::ChildError.
55
+ MiscHacks.fork_and_check do exit! 42 end
56
+ MiscHacks.fork_and_check do exec 'sh', '-c', 'exit 42' end
57
+ MiscHacks.fork_and_check do exec 'failure' end
58
+
59
+ # Does not raise an error.
60
+ MiscHacks.fork_and_check do exit! 0 end
61
+
62
+ # do_and_exit
63
+
64
+ # Prints foo if there are no failures. If anything fails, raises an
65
+ # exception.
66
+ MiscHacks.fork_and_check do
67
+ MiscHacks.do_and_exit! do
68
+ exec 'sh', '-c', 'echo foo'
69
+ end
70
+ end
71
+
72
+ # overwrite
73
+
74
+ MiscHacks.overwrite 'myconfig' do |io|
75
+ io << config.to_yaml
76
+ end
77
+
78
+ # tempname_for
79
+
80
+ MiscHacks.tempname_for '/foo/bar/baz' # => '/foo/bar/.baz.klyce3f517qkh9l'
81
+
82
+ # try_n_times
83
+
84
+ io = MiscHacks.try_n_times do
85
+ File.open path, File::RDWR|File::CREAT|File::EXCL
86
+ end
87
+
88
+ # Exception#to_formatted_string
89
+
90
+ begin
91
+ # Do something
92
+ rescue => e
93
+ warn e.to_formatted_string
94
+ end
95
+
96
+ == REQUIREMENTS:
97
+
98
+ * POSIX sh
99
+ * A system that implements fork
100
+
101
+ == INSTALL:
102
+
103
+ * sudo gem install ion1-mischacks --source http://gems.github.com/
104
+
105
+ == LICENSE:
106
+
107
+ Copyright © 2009 Johan Kiviniemi
108
+
109
+ Permission to use, copy, modify, and/or distribute this software for any
110
+ purpose with or without fee is hereby granted, provided that the above
111
+ copyright notice and this permission notice appear in all copies.
112
+
113
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
114
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
115
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
116
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
117
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
118
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
119
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,43 @@
1
+ # mischacks – Miscellaneous methods that may or may not be useful
2
+ # Copyright © 2009 Johan Kiviniemi
3
+ #
4
+ # Permission to use, copy, modify, and/or distribute this software for any
5
+ # purpose with or without fee is hereby granted, provided that the above
6
+ # copyright notice and this permission notice appear in all copies.
7
+ #
8
+ # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9
+ # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10
+ # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11
+ # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12
+ # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13
+ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14
+ # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15
+
16
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__)+'/lib')
17
+
18
+ require 'rubygems'
19
+
20
+ require 'rake'
21
+ require 'rake/clean'
22
+
23
+ task :default => :spec
24
+
25
+ begin
26
+ require 'jeweler'
27
+
28
+ Jeweler::Tasks.new do |gemspec|
29
+ gemspec.name = "mischacks"
30
+ gemspec.summary = "Miscellaneous methods that may or may not be useful"
31
+ gemspec.description = "sh: Safely pass untrusted parameters to sh scripts. overwrite: Safely replace a file. Exception#to_formatted_string: Return a string that looks like how Ruby would dump an uncaught exception."
32
+ gemspec.email = "devel@johan.kiviniemi.name"
33
+ gemspec.homepage = "http://johan.kiviniemi.name/software/mischacks/"
34
+ gemspec.authors = ["Johan Kiviniemi"]
35
+ end
36
+
37
+ Jeweler::GemcutterTasks.new
38
+
39
+ rescue LoadError
40
+ puts "Jeweler not available. Install it with: gem install jeweler"
41
+ end
42
+
43
+ # vim:set et sw=2 sts=2:
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.6
data/lib/mischacks.rb ADDED
@@ -0,0 +1,175 @@
1
+ # mischacks – Miscellaneous methods that may or may not be useful
2
+ # Copyright © 2009 Johan Kiviniemi
3
+ #
4
+ # Permission to use, copy, modify, and/or distribute this software for any
5
+ # purpose with or without fee is hereby granted, provided that the above
6
+ # copyright notice and this permission notice appear in all copies.
7
+ #
8
+ # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9
+ # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10
+ # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11
+ # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12
+ # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13
+ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14
+ # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15
+
16
+ module MiscHacks
17
+ VERSION = '0.0.5'
18
+
19
+ class ChildError < RuntimeError
20
+ attr_reader :status
21
+
22
+ def initialize status
23
+ @status = Integer status
24
+ super "Child failed with status #{status}"
25
+ end
26
+ end
27
+
28
+ def self.sh cmd, *args
29
+ env = if args.last.is_a? Hash then args.pop else {} end
30
+
31
+ fork_and_check do
32
+ do_and_exit! do
33
+ env.each_pair do |k, v| ENV[k.to_s] = v.to_s end
34
+ exec *(%W{sh -e -c #{cmd} sh} + args.map {|a| a.to_s })
35
+ end
36
+ end
37
+
38
+ nil
39
+ end
40
+
41
+ def self.fork_and_check
42
+ fork do
43
+ yield
44
+ end.tap do |pid|
45
+ _, status = Process.wait2 pid
46
+ raise ChildError, status.exitstatus if status.exitstatus != 0
47
+ end
48
+
49
+ nil
50
+ end
51
+
52
+ def self.catching_exit final_proc, fallthrough_status
53
+ status = fallthrough_status
54
+
55
+ begin
56
+ yield
57
+ rescue SystemExit => e
58
+ status = e.status
59
+ rescue Exception => e
60
+ warn e.to_formatted_string
61
+ ensure
62
+ final_proc.call status
63
+ end
64
+
65
+ status
66
+ end
67
+
68
+ def self.do_and_exit status=1, &block
69
+ catching_exit method(:exit), status, &block
70
+ end
71
+
72
+ def self.do_and_exit! status=1, &block
73
+ catching_exit method(:exit!), status, &block
74
+ end
75
+
76
+ def self.overwrite path, mode=nil
77
+ begin
78
+ stat = File.stat path
79
+
80
+ raise ArgumentError, "Not a file: #{path}", caller unless stat.file?
81
+ raise ArgumentError, "Not writable: #{path}", caller unless stat.writable?
82
+
83
+ mode ||= stat.mode & 0777
84
+ rescue Errno::ENOENT
85
+ stat = nil
86
+ end
87
+
88
+ temppath, io = try_n_times do
89
+ t = tempname_for path
90
+ io = File.open t, File::RDWR|File::CREAT|File::EXCL
91
+ [t, io]
92
+ end
93
+
94
+ begin
95
+ ret = yield io
96
+
97
+ io.best_datasync
98
+
99
+ File.chmod mode, temppath if mode
100
+
101
+ File.rename temppath, path
102
+
103
+ rescue
104
+ File.unlink temppath
105
+ raise
106
+
107
+ ensure
108
+ io.close
109
+ end
110
+
111
+ ret
112
+ end
113
+
114
+ def self.tempname_for path
115
+ dirname = File.dirname path
116
+ basename = File.basename path
117
+
118
+ '%s/.%s.%s%s%s' % [
119
+ dirname,
120
+ basename,
121
+ Time.now.to_i.to_s(36),
122
+ $$.to_s(36),
123
+ rand(1<<32).to_s(36)
124
+ ]
125
+ end
126
+
127
+ def self.try_n_times n=10
128
+ i = 0
129
+ begin
130
+ yield
131
+ rescue
132
+ i += 1
133
+ retry if i < n
134
+ raise
135
+ end
136
+ end
137
+ end
138
+
139
+ module MiscHacks
140
+ module ExceptionMixin
141
+ def to_formatted_string
142
+ bt = backtrace.dup
143
+ head = bt.shift
144
+ (
145
+ ["#{head}: #{self} (#{self.class})"] +
146
+ bt.map do |l| "\tfrom #{l}" end
147
+ ).map do |l| "#{l}\n" end.join
148
+ end
149
+ end
150
+ end
151
+
152
+ Exception.class_eval do
153
+ include MiscHacks::ExceptionMixin
154
+ end
155
+
156
+ module MiscHacks
157
+ module IOMixin
158
+ def best_datasync
159
+ meths = [:fdatasync, :fsync, :flush]
160
+
161
+ begin
162
+ send meths.shift
163
+ rescue NoMethodError, NotImplementedError
164
+ retry unless meths.empty?
165
+ raise
166
+ end
167
+ end
168
+ end
169
+ end
170
+
171
+ IO.class_eval do
172
+ include MiscHacks::IOMixin
173
+ end
174
+
175
+ # vim:set et sw=2 sts=2:
@@ -0,0 +1,393 @@
1
+ # mischacks – Miscellaneous methods that may or may not be useful
2
+ # Copyright © 2009 Johan Kiviniemi
3
+ #
4
+ # Permission to use, copy, modify, and/or distribute this software for any
5
+ # purpose with or without fee is hereby granted, provided that the above
6
+ # copyright notice and this permission notice appear in all copies.
7
+ #
8
+ # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9
+ # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10
+ # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11
+ # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12
+ # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13
+ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14
+ # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15
+
16
+ require File.expand_path(File.dirname(__FILE__)+'/spec_helper.rb')
17
+ require 'mischacks'
18
+
19
+ require 'digest/sha1'
20
+ require 'fileutils'
21
+ require 'set'
22
+ require 'tmpdir'
23
+
24
+ mh = MiscHacks
25
+ ce = MiscHacks::ChildError
26
+
27
+ describe mh do
28
+ describe 'sh' do
29
+ unsafe_str = %q{" 'foo' $(bar) `baz` "}
30
+ checksum = Digest::SHA1.hexdigest unsafe_str
31
+
32
+ it "should raise #{ce} on error" do
33
+ good = ['true', '', 'printf ""']
34
+ bad = ['false', 'exit 2', 'return 2']
35
+
36
+ bad.each do |c|
37
+ lambda do mh.sh c end.should raise_error ce
38
+ end
39
+
40
+ good.each do |c|
41
+ lambda do mh.sh c end.should_not raise_error
42
+ end
43
+ end
44
+
45
+ it 'should call sh with -e' do
46
+ lambda do mh.sh 'false; true' end.should raise_error ce
47
+ end
48
+
49
+ it 'should pass normal parameters safely' do
50
+ test = lambda do |str, sha|
51
+ mh.sh %q{
52
+ temp="$(mktemp -t mischacks.XXXXXXXXXX)"
53
+ trap 'rm -f "$temp"' 0 1 2 13 15
54
+ printf "%s" "$1" >"$temp"
55
+ printf "%s *%s\n" "$2" "$temp" | sha1sum -c >/dev/null 2>&1
56
+ }, str, sha
57
+ end
58
+
59
+ lambda do test.call unsafe_str, checksum end.should_not raise_error
60
+ lambda do test.call 'foo', checksum end.should raise_error ce
61
+ end
62
+
63
+ it 'should pass environment variables safely' do
64
+ test = lambda do |str, sha|
65
+ mh.sh %q{
66
+ temp="$(mktemp -t mischacks.XXXXXXXXXX)"
67
+ trap 'rm -f "$temp"' 0 1 2 13 15
68
+ printf "%s" "$string" >"$temp"
69
+ printf "%s *%s\n" "$checksum" "$temp" | sha1sum -c >/dev/null 2>&1
70
+ }, :string => str, :checksum => sha
71
+ end
72
+
73
+ lambda do test.call unsafe_str, checksum end.should_not raise_error
74
+ lambda do test.call 'foo', checksum end.should raise_error ce
75
+ end
76
+ end
77
+
78
+ describe 'fork_and_check' do
79
+ it 'should raise an error when child fails' do
80
+ lambda do mh.fork_and_check do exit 1 end end.should raise_error ce
81
+ lambda do mh.fork_and_check do exit! 1 end end.should raise_error ce
82
+
83
+ lambda do mh.fork_and_check do exit 0 end end.should_not raise_error
84
+ lambda do mh.fork_and_check do exit! 0 end end.should_not raise_error
85
+ end
86
+
87
+ it 'should handle exec' do
88
+ lambda do mh.fork_and_check do exec 'false' end end.should raise_error ce
89
+ lambda do mh.fork_and_check do exec 'true' end end.should_not raise_error
90
+ end
91
+ end
92
+
93
+ describe 'catching_exit' do
94
+ it 'should call final_proc with the fallthrough status when the block does not exit' do
95
+ [0, 1, 42, 255].each do |i|
96
+ foo = nil
97
+ mh.catching_exit(lambda {|status| foo = status }, 2) do end
98
+ foo.should == 2
99
+ end
100
+
101
+ end
102
+
103
+ it 'should call final_proc with the exit status when the block exits' do
104
+ [0, 1, 42, 255].each do |i|
105
+ foo = nil
106
+ mh.catching_exit(lambda {|status| foo = status }, 2) do exit i end
107
+ foo.should == i
108
+ end
109
+ end
110
+ end
111
+
112
+ describe 'do_and_exit' do
113
+ it 'should have the proper exit status when the block does not exit' do
114
+ lambda do
115
+ mh.do_and_exit do end
116
+ exit 2 # Should never reach this.
117
+ end.should exit_with 1
118
+
119
+ lambda do
120
+ mh.do_and_exit! do end
121
+ exit! 2 # Should never reach this.
122
+ end.should exit_with 1
123
+
124
+ [0, 1, 42, 255].each do |i|
125
+ lambda do
126
+ mh.do_and_exit i do end
127
+ exit 2 # Should never reach this.
128
+ end.should exit_with i
129
+ end
130
+
131
+ [0, 1, 42, 255].each do |i|
132
+ lambda do
133
+ mh.do_and_exit! i do end
134
+ exit! 2 # Should never reach this.
135
+ end.should exit_with i
136
+ end
137
+ end
138
+
139
+ it 'should have the proper exit status when the block exits' do
140
+ [0, 1, 42, 255].each do |i|
141
+ lambda do
142
+ mh.do_and_exit do exit i end
143
+ exit 2 # Should never reach this.
144
+ end.should exit_with i
145
+ end
146
+
147
+ [0, 1, 42, 255].each do |i|
148
+ lambda do
149
+ mh.do_and_exit! do exit! i end
150
+ exit! 2 # Should never reach this.
151
+ end.should exit_with i
152
+ end
153
+ end
154
+
155
+ it 'should handle exec' do
156
+ [0, 1, 42, 255].each do |i|
157
+ lambda do
158
+ mh.do_and_exit! do exec *%W{sh -c #{'exit "$1"'} sh #{i}} end
159
+ exit! 2 # Should never reach this.
160
+ end.should exit_with i
161
+ end
162
+ end
163
+
164
+ it 'should handle exit! and plain exit' do
165
+ lambda do
166
+ begin
167
+ mh.do_and_exit do end
168
+ rescue SystemExit => e
169
+ exit 2
170
+ end
171
+ end.should exit_with 2
172
+
173
+ lambda do
174
+ begin
175
+ mh.do_and_exit! do end
176
+ rescue SystemExit => e
177
+ exit 2 # Should never reach this.
178
+ end
179
+ end.should exit_with 1
180
+ end
181
+ end
182
+
183
+ describe 'overwrite' do
184
+ before :all do
185
+ @dir = Dir.mktmpdir
186
+ @file = "#{@dir}/foo"
187
+ @content0 = "bar\n"
188
+ @content1 = "baz\n"
189
+ @mode0 = 0714
190
+ @mode1 = 0755
191
+ end
192
+
193
+ after :all do
194
+ FileUtils.rm_rf @dir
195
+ end
196
+
197
+ it 'should create a fresh file' do
198
+ mh.overwrite @file do |io|
199
+ io << @content0
200
+ File.exists?(@file).should == false
201
+ end
202
+ File.read(@file).should == @content0
203
+ end
204
+
205
+ it 'should overwrite a file' do
206
+ mh.overwrite @file do |io|
207
+ io << @content1
208
+ File.read(@file).should == @content0
209
+ end
210
+ File.read(@file).should == @content1
211
+ end
212
+
213
+ it 'should retain the mode' do
214
+ File.chmod @mode0, @file
215
+ mh.overwrite @file do end
216
+ (File.stat(@file).mode & 07777).should == @mode0
217
+ end
218
+
219
+ it 'should set the mode' do
220
+ mh.overwrite @file, @mode1 do end
221
+ (File.stat(@file).mode & 07777).should == @mode1
222
+ end
223
+ end
224
+
225
+ describe 'tempname_for' do
226
+ it 'should generate an unique temporary path' do
227
+ path = '/foo/bar/baz'
228
+ tempnames = (0...10).map { mh.tempname_for(path) }.to_set
229
+
230
+ # It is very, very unlikely there are duplicates.
231
+ tempnames.length.should > 8
232
+
233
+ tempnames.each do |n|
234
+ n.should =~ %r{\A/foo/bar/.baz.[a-z0-9]+\z}
235
+ end
236
+ end
237
+ end
238
+
239
+ describe 'try_n_times' do
240
+ it 'should try 10 times' do
241
+ e = Class.new RuntimeError
242
+
243
+ i = 0
244
+ mh.try_n_times do
245
+ i += 1
246
+ 42
247
+ end.should == 42
248
+ i.should == 1
249
+
250
+ i = 0
251
+ mh.try_n_times do
252
+ i += 1
253
+ raise e if i < 10
254
+ 42
255
+ end.should == 42
256
+ i.should == 10
257
+
258
+ i = 0
259
+ lambda do
260
+ mh.try_n_times do
261
+ i += 1
262
+ raise e if i < 11
263
+ 42
264
+ end
265
+ end.should raise_error e
266
+ i.should == 10
267
+ end
268
+ end
269
+
270
+ describe 'ExceptionMixin to_formatted_string' do
271
+ it 'should return the proper string for an exception' do
272
+ klass = Class.new RuntimeError
273
+
274
+ begin
275
+ raise klass, 'foo'
276
+
277
+ rescue klass => e
278
+ lines = e.to_formatted_string.split /\n/
279
+ head = lines.shift
280
+
281
+ e_class_re = Regexp.quote e.class.to_s
282
+ e_msg_re = Regexp.quote e.to_s
283
+ filename_re = Regexp.quote File.basename(__FILE__)
284
+
285
+ head_re = %r{\A.+/#{filename_re}:\d+: #{e_msg_re} \(#{e_class_re}\)\z}
286
+ head.should match head_re
287
+
288
+ lines.length.should == caller.length
289
+ lines.zip caller do |a, b|
290
+ a.should == "\tfrom #{b}"
291
+ end
292
+ end
293
+ end
294
+ end
295
+
296
+ describe 'IOMixin best_datasync' do
297
+ before :all do
298
+ @dir = Dir.mktmpdir
299
+ @file = "#{@dir}/foo"
300
+ @content = "f"
301
+ end
302
+
303
+ after :all do
304
+ FileUtils.rm_rf @dir
305
+ end
306
+
307
+ it 'should flush any buffered data to the OS' do
308
+ open @file, 'w' do |io|
309
+ io << @content
310
+
311
+ File.read(@file).should == ''
312
+
313
+ io.best_datasync
314
+
315
+ File.read(@file).should == @content
316
+ end
317
+ end
318
+
319
+ sync_meths = [:fdatasync, :fsync, :flush]
320
+
321
+ sync_meths.length.times do |i|
322
+ sync_meths_dup = sync_meths.dup
323
+
324
+ fails = sync_meths_dup.shift i
325
+ succeeds = sync_meths_dup.shift
326
+ ignored = sync_meths_dup
327
+
328
+ desc = "should call #{succeeds}"
329
+
330
+ if fails.empty?
331
+ desc << " if available"
332
+ else
333
+ desc << " if #{fails.join(', ')} unavailable"
334
+ end
335
+
336
+ desc << " (ignoring #{ignored.join(', ')})" unless ignored.empty?
337
+
338
+ it desc do
339
+ [NoMethodError, NotImplementedError].each do |error|
340
+ open @file, 'w' do |io|
341
+ fails.each do |meth|
342
+ if error == NoMethodError
343
+ io.metaclass.send :undef_method, meth rescue nil
344
+ else
345
+ io.should_receive(meth).once.ordered.and_raise error
346
+ end
347
+ end
348
+
349
+ io.should_receive(succeeds).once.ordered.and_return nil
350
+
351
+ ignored.each do |meth|
352
+ io.should_not_receive(meth)
353
+ end
354
+
355
+ io.best_datasync
356
+ end
357
+ end
358
+ end
359
+ end
360
+
361
+ it "should fail if none of #{sync_meths.join(', ')} are available" do
362
+ [NoMethodError, NotImplementedError].each do |error|
363
+ lambda do
364
+ open @file, 'w' do |io|
365
+ sync_meths.each do |meth|
366
+ if error == NoMethodError
367
+ io.metaclass.send :undef_method, meth rescue nil
368
+ else
369
+ io.should_receive(meth).once.ordered.and_raise error
370
+ end
371
+ end
372
+
373
+ io.best_datasync
374
+ end
375
+ end.should raise_error error
376
+ end
377
+ end
378
+
379
+ it 'should pass other failures through' do
380
+ lambda do
381
+ open @file, 'w' do |io|
382
+ io.should_receive(:fdatasync).once.ordered.and_raise NotImplementedError
383
+ io.should_receive(:fsync).once.ordered.and_raise IOError
384
+ io.should_not_receive(:flush)
385
+
386
+ io.best_datasync
387
+ end
388
+ end.should raise_error IOError
389
+ end
390
+ end
391
+ end
392
+
393
+ # vim:set et sw=2 sts=2:
@@ -0,0 +1,50 @@
1
+ # mischacks – Miscellaneous methods that may or may not be useful
2
+ # Copyright © 2009 Johan Kiviniemi
3
+ #
4
+ # Permission to use, copy, modify, and/or distribute this software for any
5
+ # purpose with or without fee is hereby granted, provided that the above
6
+ # copyright notice and this permission notice appear in all copies.
7
+ #
8
+ # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9
+ # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10
+ # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11
+ # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12
+ # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13
+ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14
+ # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15
+
16
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__)+'/../lib')
17
+
18
+ require 'mischacks'
19
+
20
+ class Object
21
+ def metaclass
22
+ class << self; self; end
23
+ end
24
+ end
25
+
26
+ Spec::Matchers.define :exit_with do |expected|
27
+ match do |block|
28
+ @status = 0
29
+
30
+ begin
31
+ MiscHacks.fork_and_check do
32
+ block.call
33
+ end
34
+ rescue MiscHacks::ChildError => e
35
+ @status = e.status
36
+ end
37
+
38
+ @status.eql? expected
39
+ end
40
+
41
+ failure_message_for_should do |block|
42
+ "expected exit value #{expected}, got #{@status}"
43
+ end
44
+
45
+ failure_message_for_should do |block|
46
+ "did not expect exit value #{@status}"
47
+ end
48
+ end
49
+
50
+ # vim:set et sw=2 sts=2:
metadata ADDED
@@ -0,0 +1,64 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mischacks
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.6
5
+ platform: ruby
6
+ authors:
7
+ - Johan Kiviniemi
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-02-14 00:00:00 +02:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: "sh: Safely pass untrusted parameters to sh scripts. overwrite: Safely replace a file. Exception#to_formatted_string: Return a string that looks like how Ruby would dump an uncaught exception."
17
+ email: devel@johan.kiviniemi.name
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.txt
24
+ files:
25
+ - COPYING
26
+ - History.txt
27
+ - Manifest.txt
28
+ - README.txt
29
+ - Rakefile
30
+ - VERSION
31
+ - lib/mischacks.rb
32
+ - spec/mischacks_spec.rb
33
+ - spec/spec_helper.rb
34
+ has_rdoc: true
35
+ homepage: http://johan.kiviniemi.name/software/mischacks/
36
+ licenses: []
37
+
38
+ post_install_message:
39
+ rdoc_options:
40
+ - --charset=UTF-8
41
+ require_paths:
42
+ - lib
43
+ required_ruby_version: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: "0"
48
+ version:
49
+ required_rubygems_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: "0"
54
+ version:
55
+ requirements: []
56
+
57
+ rubyforge_project:
58
+ rubygems_version: 1.3.5
59
+ signing_key:
60
+ specification_version: 3
61
+ summary: Miscellaneous methods that may or may not be useful
62
+ test_files:
63
+ - spec/spec_helper.rb
64
+ - spec/mischacks_spec.rb