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 +13 -0
- data/History.txt +25 -0
- data/Manifest.txt +9 -0
- data/README.txt +119 -0
- data/Rakefile +43 -0
- data/VERSION +1 -0
- data/lib/mischacks.rb +175 -0
- data/spec/mischacks_spec.rb +393 -0
- data/spec/spec_helper.rb +50 -0
- metadata +64 -0
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
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:
|
data/spec/spec_helper.rb
ADDED
@@ -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
|