cond 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +334 -0
- data/Rakefile +195 -0
- data/cond.gemspec +37 -0
- data/examples/bad_example.rb +51 -0
- data/examples/calc_example.rb +84 -0
- data/examples/readme_example.rb +27 -0
- data/examples/restarts_example.rb +26 -0
- data/examples/seibel_example.rb +18 -0
- data/install.rb +3 -0
- data/lib/cond.rb +456 -0
- data/lib/cond/cond_private/defaults.rb +78 -0
- data/lib/cond/cond_private/symbol_generator.rb +45 -0
- data/lib/cond/cond_private/thread_local.rb +77 -0
- data/readmes/restarts.rb +84 -0
- data/readmes/seibel_pcl.rb +129 -0
- data/spec/basic_spec.rb +66 -0
- data/spec/common.rb +49 -0
- data/spec/error_spec.rb +55 -0
- data/spec/leave_again_spec.rb +88 -0
- data/spec/matching_spec.rb +86 -0
- data/spec/raise_spec.rb +69 -0
- data/spec/reraise_spec.rb +101 -0
- data/spec/specs_spec.rb +16 -0
- data/spec/symbols_spec.rb +33 -0
- data/spec/thread_local_spec.rb +29 -0
- data/spec/wrapping_spec.rb +93 -0
- data/support/quix/ruby.rb +51 -0
- data/support/quix/simple_installer.rb +88 -0
- metadata +93 -0
data/spec/common.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
$LOAD_PATH.unshift File.dirname(__FILE__) + "/../lib"
|
2
|
+
$LOAD_PATH.unshift File.dirname(__FILE__) + "/../support"
|
3
|
+
|
4
|
+
# darn rspec warnings
|
5
|
+
$VERBOSE = false
|
6
|
+
begin
|
7
|
+
require 'spec'
|
8
|
+
rescue LoadError
|
9
|
+
require 'rubygems'
|
10
|
+
require 'spec'
|
11
|
+
end
|
12
|
+
|
13
|
+
# NOTE: In jruby this must come after require 'rubygems'
|
14
|
+
require 'cond'
|
15
|
+
|
16
|
+
require 'pathname'
|
17
|
+
require 'stringio'
|
18
|
+
|
19
|
+
def pipe_to_ruby(code)
|
20
|
+
require 'quix/ruby'
|
21
|
+
IO.popen(%{"#{Quix::Ruby::EXECUTABLE}"}, "r+") { |pipe|
|
22
|
+
pipe.puts code
|
23
|
+
pipe.flush
|
24
|
+
pipe.close_write
|
25
|
+
pipe.read
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
def capture(input_string)
|
30
|
+
previous = [
|
31
|
+
$stdout, $stdin, Cond.defaults.stream_out, Cond.defaults.stream_in
|
32
|
+
]
|
33
|
+
begin
|
34
|
+
StringIO.open("", "r+") { |output|
|
35
|
+
StringIO.open(input_string) { |input|
|
36
|
+
Cond.defaults.stream_out = output
|
37
|
+
Cond.defaults.stream_in = input
|
38
|
+
$stdout = output
|
39
|
+
$stdin = input
|
40
|
+
yield
|
41
|
+
output.rewind
|
42
|
+
output.read
|
43
|
+
}
|
44
|
+
}
|
45
|
+
ensure
|
46
|
+
$stdout, $stdin, Cond.defaults.stream_out, Cond.defaults.stream_in =
|
47
|
+
previous
|
48
|
+
end
|
49
|
+
end
|
data/spec/error_spec.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/common"
|
2
|
+
|
3
|
+
include Cond
|
4
|
+
|
5
|
+
describe "error reporting" do
|
6
|
+
it "should raise NoRestartError when restart is not found" do
|
7
|
+
lambda {
|
8
|
+
invoke_restart(:zzz)
|
9
|
+
}.should raise_error(Cond::NoRestartError)
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should raise ContextError when restart called outside " +
|
13
|
+
"restartable block" do
|
14
|
+
lambda {
|
15
|
+
restart :zzz do
|
16
|
+
end
|
17
|
+
}.should raise_error(Cond::ContextError)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should raise ContextError when restart called inside handling block" do
|
21
|
+
lambda {
|
22
|
+
handling do
|
23
|
+
restart :zzz do
|
24
|
+
end
|
25
|
+
end
|
26
|
+
}.should raise_error(Cond::ContextError)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should raise ContextError when handle called outside handling block" do
|
30
|
+
lambda {
|
31
|
+
handle RuntimeError do
|
32
|
+
end
|
33
|
+
}.should raise_error(Cond::ContextError)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should raise ContextError when restart called inside handling block" do
|
37
|
+
lambda {
|
38
|
+
handling do
|
39
|
+
restart :zzz do
|
40
|
+
end
|
41
|
+
end
|
42
|
+
}.should raise_error(Cond::ContextError)
|
43
|
+
end
|
44
|
+
|
45
|
+
[:leave, :again].each { |keyword|
|
46
|
+
desc =
|
47
|
+
"should raise ContextError when #{keyword} called outside " +
|
48
|
+
"restartable or handling block"
|
49
|
+
it desc do
|
50
|
+
lambda {
|
51
|
+
send keyword
|
52
|
+
}.should raise_error(Cond::ContextError)
|
53
|
+
end
|
54
|
+
}
|
55
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/common"
|
2
|
+
|
3
|
+
include Cond
|
4
|
+
|
5
|
+
[:handling, :restartable].each { |keyword|
|
6
|
+
describe "arguments to 'leave' have semantics of 'return'" do
|
7
|
+
it "should be passed to the #{keyword} block result (none)" do
|
8
|
+
send(keyword) do
|
9
|
+
leave
|
10
|
+
end.should == nil
|
11
|
+
end
|
12
|
+
it "should be passed to the #{keyword} block result (single)" do
|
13
|
+
send(keyword) do
|
14
|
+
leave 3
|
15
|
+
end.should == 3
|
16
|
+
end
|
17
|
+
it "should be passed to the #{keyword} block result (multiple)" do
|
18
|
+
send(keyword) do
|
19
|
+
leave 4, 5
|
20
|
+
end.should == [4, 5]
|
21
|
+
end
|
22
|
+
it "should be passed to the #{keyword} block result (single array)" do
|
23
|
+
send(keyword) do
|
24
|
+
leave([6, 7])
|
25
|
+
end.should == [6, 7]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
}
|
29
|
+
|
30
|
+
[:handling, :restartable].each { |keyword|
|
31
|
+
describe "arguments to 'again' have semantics of 'return'" do
|
32
|
+
before :each do
|
33
|
+
@memo = []
|
34
|
+
end
|
35
|
+
it "should be passed to the #{keyword} block args (none)" do
|
36
|
+
send(keyword) do |*args|
|
37
|
+
@memo.push :visit
|
38
|
+
if @memo.size == 2
|
39
|
+
args.should == []
|
40
|
+
again
|
41
|
+
elsif @memo.size == 3
|
42
|
+
args.should == []
|
43
|
+
leave
|
44
|
+
end
|
45
|
+
again
|
46
|
+
end
|
47
|
+
end
|
48
|
+
it "should be passed to the #{keyword} block args (single)" do
|
49
|
+
send(keyword) do |*args|
|
50
|
+
@memo.push :visit
|
51
|
+
if @memo.size == 2
|
52
|
+
args.should == [3]
|
53
|
+
again
|
54
|
+
elsif @memo.size == 3
|
55
|
+
args.should == []
|
56
|
+
leave
|
57
|
+
end
|
58
|
+
again 3
|
59
|
+
end
|
60
|
+
end
|
61
|
+
it "should be passed to the #{keyword} block args (multiple)" do
|
62
|
+
send(keyword) do |*args|
|
63
|
+
@memo.push :visit
|
64
|
+
if @memo.size == 2
|
65
|
+
args.should == [4, 5]
|
66
|
+
again
|
67
|
+
elsif @memo.size == 3
|
68
|
+
args.should == []
|
69
|
+
leave
|
70
|
+
end
|
71
|
+
again 4, 5
|
72
|
+
end
|
73
|
+
end
|
74
|
+
it "should be passed to the #{keyword} block args (single array)" do
|
75
|
+
send(keyword) do |*args|
|
76
|
+
@memo.push :visit
|
77
|
+
if @memo.size == 2
|
78
|
+
args.should == [6, 7]
|
79
|
+
again
|
80
|
+
elsif @memo.size == 3
|
81
|
+
args.should == []
|
82
|
+
leave
|
83
|
+
end
|
84
|
+
again [6, 7]
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
}
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/common"
|
2
|
+
|
3
|
+
class AnimalError < StandardError ; end
|
4
|
+
class BirdError < AnimalError ; end
|
5
|
+
class SparrowError < BirdError ; end
|
6
|
+
class DogError < AnimalError ; end
|
7
|
+
class RawError < Exception ; end
|
8
|
+
|
9
|
+
RANDOM_ERRORS = [
|
10
|
+
Exception,
|
11
|
+
RuntimeError,
|
12
|
+
AnimalError,
|
13
|
+
BirdError,
|
14
|
+
SparrowError,
|
15
|
+
DogError,
|
16
|
+
RawError,
|
17
|
+
]
|
18
|
+
|
19
|
+
describe "handler matching system" do
|
20
|
+
before :all do
|
21
|
+
@memo = []
|
22
|
+
@handlers = RANDOM_ERRORS.inject(Hash.new) { |acc, ex|
|
23
|
+
acc.merge!(ex => lambda { |t| @memo.push ex })
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should find an exact match when it is the only option" do
|
28
|
+
@memo.clear
|
29
|
+
Cond.with_handlers(DogError => @handlers[DogError]) {
|
30
|
+
raise DogError
|
31
|
+
}
|
32
|
+
@memo.should == [DogError]
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should find an exact match among other matches" do
|
36
|
+
@memo.clear
|
37
|
+
Cond.with_handlers(@handlers) {
|
38
|
+
raise DogError
|
39
|
+
}
|
40
|
+
@memo.should == [DogError]
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should find a match given an Exception instance" do
|
44
|
+
@memo.clear
|
45
|
+
Cond.with_handlers(@handlers) {
|
46
|
+
raise DogError.new
|
47
|
+
}
|
48
|
+
@memo.should == [DogError]
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should not find non-matches" do
|
52
|
+
lambda {
|
53
|
+
Cond.with_handlers(BirdError => lambda { |e| }) {
|
54
|
+
raise DogError
|
55
|
+
}
|
56
|
+
}.should raise_error(DogError)
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should find a related match" do
|
60
|
+
@memo.clear
|
61
|
+
Cond.with_handlers(BirdError => @handlers[BirdError]) {
|
62
|
+
raise SparrowError
|
63
|
+
}
|
64
|
+
@memo.should == [BirdError]
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should find the closest related match" do
|
68
|
+
@memo.clear
|
69
|
+
handlers = {
|
70
|
+
BirdError => @handlers[BirdError],
|
71
|
+
AnimalError => @handlers[AnimalError],
|
72
|
+
}
|
73
|
+
Cond.with_handlers(handlers) {
|
74
|
+
raise SparrowError
|
75
|
+
}
|
76
|
+
@memo.should == [BirdError]
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should work with the catch-all handler" do
|
80
|
+
@memo.clear
|
81
|
+
Cond.with_handlers(@handlers) {
|
82
|
+
raise SyntaxError
|
83
|
+
}
|
84
|
+
@memo.should == [Exception]
|
85
|
+
end
|
86
|
+
end
|
data/spec/raise_spec.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/common"
|
2
|
+
|
3
|
+
describe "raise replacement" do
|
4
|
+
it "should raise" do
|
5
|
+
lambda {
|
6
|
+
raise NameError
|
7
|
+
}.should raise_error(NameError)
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should raise RuntimeError for 'raise \"string\"'" do
|
11
|
+
lambda {
|
12
|
+
raise "The mass of men lead lives of quiet desperation. --Thoreau"
|
13
|
+
}.should raise_error(RuntimeError)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should accept the three-argument form" do
|
17
|
+
begin
|
18
|
+
raise RuntimeError, "msg", ["zz"]
|
19
|
+
rescue => e
|
20
|
+
[e.class, e.message, e.backtrace].should == [RuntimeError, "msg", ["zz"]]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should raise TypeError if the third arg is not array of strings" do
|
25
|
+
ex = nil
|
26
|
+
begin
|
27
|
+
raise RuntimeError, "zz", Hash.new
|
28
|
+
rescue Exception => ex
|
29
|
+
ex = ex
|
30
|
+
end
|
31
|
+
ex.class.should == TypeError
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should raise TypeError for random junk arguments" do
|
35
|
+
lambda {
|
36
|
+
raise "a", "b"
|
37
|
+
}.should raise_error(TypeError)
|
38
|
+
lambda {
|
39
|
+
raise 1, "b"
|
40
|
+
}.should raise_error(TypeError)
|
41
|
+
lambda {
|
42
|
+
raise RuntimeError, "msg", 33
|
43
|
+
}.should raise_error(TypeError)
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should accept a non-Exception non-String which responds to #exception" do
|
47
|
+
klass = Class.new {
|
48
|
+
def exception
|
49
|
+
RuntimeError.new("my exception")
|
50
|
+
end
|
51
|
+
}
|
52
|
+
begin
|
53
|
+
raise klass.new
|
54
|
+
rescue => e
|
55
|
+
e.message.should == "my exception"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should raise TypeError if the argument is not a String, not " +
|
60
|
+
"an Exception, and does not respond to #exception" do
|
61
|
+
lambda {
|
62
|
+
raise 27
|
63
|
+
}.should raise_error(TypeError)
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should be aliased to 'fail'" do
|
67
|
+
Kernel.instance_method(:raise).should == Kernel.instance_method(:fail)
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/common"
|
2
|
+
|
3
|
+
class ReraiseExampleError < Exception
|
4
|
+
end
|
5
|
+
|
6
|
+
include Cond
|
7
|
+
|
8
|
+
describe "re-raise" do
|
9
|
+
it "should work work with no arguments" do
|
10
|
+
lambda {
|
11
|
+
handling do
|
12
|
+
handle ReraiseExampleError do
|
13
|
+
raise
|
14
|
+
end
|
15
|
+
raise ReraiseExampleError
|
16
|
+
end
|
17
|
+
}.should raise_error(ReraiseExampleError)
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "with arguments" do
|
21
|
+
before :all do
|
22
|
+
@memo = []
|
23
|
+
@func = lambda {
|
24
|
+
begin
|
25
|
+
handling do
|
26
|
+
handle ReraiseExampleError do
|
27
|
+
raise "---test"
|
28
|
+
end
|
29
|
+
raise ReraiseExampleError
|
30
|
+
end
|
31
|
+
rescue Exception => e
|
32
|
+
@memo.push e
|
33
|
+
raise
|
34
|
+
end
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should raise the new exception" do
|
39
|
+
@func.should raise_error(RuntimeError)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should preserve 'message'" do
|
43
|
+
@memo.first.message.should == "---test"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "nested" do
|
48
|
+
before :all do
|
49
|
+
@memo = []
|
50
|
+
@func = lambda {
|
51
|
+
handling do
|
52
|
+
handle ReraiseExampleError do
|
53
|
+
@memo.push :outer
|
54
|
+
end
|
55
|
+
handling do
|
56
|
+
handle ReraiseExampleError do
|
57
|
+
@memo.push :inner
|
58
|
+
raise
|
59
|
+
end
|
60
|
+
raise ReraiseExampleError
|
61
|
+
end
|
62
|
+
end
|
63
|
+
}
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should transfer to handlers earlier in the stack" do
|
67
|
+
@func.should_not raise_error
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should be ordered inside to outside" do
|
71
|
+
@memo.should == [:inner, :outer]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe "nested with empty inner blocks" do
|
76
|
+
before :all do
|
77
|
+
@memo = []
|
78
|
+
@func = lambda {
|
79
|
+
handling do
|
80
|
+
handle ReraiseExampleError do
|
81
|
+
@memo.push :outer
|
82
|
+
raise
|
83
|
+
end
|
84
|
+
handling do
|
85
|
+
handling do
|
86
|
+
raise ReraiseExampleError
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
}
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should transfer to handlers earlier in the stack" do
|
94
|
+
@func.should raise_error(ReraiseExampleError)
|
95
|
+
end
|
96
|
+
|
97
|
+
it "should call the re-raising handler once" do
|
98
|
+
@memo.should == [:outer]
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|