mischacks 0.0.6

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/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