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