seeing_is_believing 0.0.3 → 0.0.4
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/Gemfile.lock +3 -1
- data/Readme.md +28 -6
- data/bin/seeing_is_believing +2 -3
- data/features/binary.feature +89 -0
- data/features/step_definitions/steps.rb +2 -2
- data/features/support/env.rb +15 -2
- data/lib/seeing_is_believing/error.rb +7 -0
- data/lib/seeing_is_believing/evaluate_by_moving_files.rb +118 -0
- data/lib/seeing_is_believing/example_use.rb +64 -18
- data/lib/seeing_is_believing/expression_list.rb +35 -23
- data/lib/seeing_is_believing/hard_core_ensure.rb +52 -0
- data/lib/seeing_is_believing/has_exception.rb +6 -0
- data/lib/seeing_is_believing/result.rb +26 -20
- data/lib/seeing_is_believing/syntax_analyzer.rb +119 -37
- data/lib/seeing_is_believing/the_matrix.rb +19 -0
- data/lib/seeing_is_believing/tracks_line_numbers_seen.rb +18 -0
- data/lib/seeing_is_believing/version.rb +1 -1
- data/lib/seeing_is_believing.rb +54 -13
- data/seeing_is_believing.gemspec +1 -0
- data/spec/evaluate_by_moving_files_spec.rb +68 -0
- data/spec/expression_list_spec.rb +22 -4
- data/spec/hard_core_ensure_spec.rb +73 -0
- data/spec/seeing_is_believing_spec.rb +94 -11
- data/spec/syntax_analyzer_spec.rb +112 -1
- metadata +27 -6
@@ -1,50 +1,56 @@
|
|
1
|
-
|
1
|
+
require 'seeing_is_believing/has_exception'
|
2
|
+
require 'seeing_is_believing/tracks_line_numbers_seen'
|
2
3
|
|
4
|
+
class SeeingIsBelieving
|
3
5
|
class Result
|
4
|
-
|
5
|
-
|
6
|
-
|
6
|
+
|
7
|
+
Line = Class.new(Array) { include HasException }
|
8
|
+
|
9
|
+
include HasException
|
10
|
+
include TracksLineNumbersSeen
|
11
|
+
|
12
|
+
attr_accessor :stdout, :stderr
|
13
|
+
|
14
|
+
def has_stdout?
|
15
|
+
stdout && !stdout.empty?
|
7
16
|
end
|
8
17
|
|
9
|
-
|
18
|
+
def has_stderr?
|
19
|
+
stderr && !stderr.empty?
|
20
|
+
end
|
10
21
|
|
11
22
|
def initialize
|
12
23
|
@min_line_number = @max_line_number = 1
|
13
24
|
end
|
14
25
|
|
15
26
|
def record_result(line_number, value)
|
16
|
-
|
17
|
-
results
|
27
|
+
track_line_number line_number
|
28
|
+
results(line_number) << value.inspect
|
18
29
|
value
|
19
30
|
end
|
20
31
|
|
21
32
|
def record_exception(line_number, exception)
|
22
|
-
|
23
|
-
|
33
|
+
self.exception = exception
|
34
|
+
track_line_number line_number
|
35
|
+
results(line_number).exception = exception
|
24
36
|
end
|
25
37
|
|
26
38
|
def [](line_number)
|
27
|
-
results
|
39
|
+
results(line_number)
|
28
40
|
end
|
29
41
|
|
30
|
-
# probably not really useful, just exists to satisfy the tests
|
42
|
+
# probably not really useful, just exists to satisfy the tests, which specified too simple of an interface
|
31
43
|
def to_a
|
32
44
|
(min_line_number..max_line_number).map do |line_number|
|
33
45
|
[line_number, [*self[line_number], *Array(self[line_number].exception)]]
|
34
46
|
end
|
35
47
|
end
|
36
48
|
|
37
|
-
def contains_line_number(line_number)
|
38
|
-
@min_line_number = line_number if line_number < @min_line_number
|
39
|
-
@max_line_number = line_number if line_number > @max_line_number
|
40
|
-
end
|
41
|
-
|
42
49
|
private
|
43
50
|
|
44
|
-
def results
|
45
|
-
@results ||= Hash.new
|
46
|
-
|
47
|
-
end
|
51
|
+
def results(line_number)
|
52
|
+
@results ||= Hash.new
|
53
|
+
@results[line_number] ||= Line.new
|
48
54
|
end
|
49
55
|
end
|
50
56
|
end
|
@@ -1,49 +1,131 @@
|
|
1
1
|
require 'ripper'
|
2
2
|
|
3
|
-
class
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
3
|
+
class SeeingIsBelieving
|
4
|
+
class SyntaxAnalyzer < Ripper::SexpBuilder
|
5
|
+
|
6
|
+
# HELPERS
|
7
|
+
|
8
|
+
def self.parsed(code)
|
9
|
+
instance = new code
|
10
|
+
instance.parse
|
11
|
+
instance
|
12
|
+
end
|
13
|
+
|
14
|
+
# We have to do this b/c Ripper sometimes calls on_tstring_end even when the string doesn't get ended
|
15
|
+
# e.g. SyntaxAnalyzer.new('"a').parse
|
16
|
+
def ends_match?(beginning, ending)
|
17
|
+
return false unless beginning && ending
|
18
|
+
return beginning == ending if beginning.size == 1
|
19
|
+
case beginning[-1]
|
20
|
+
when '<' then '>' == ending
|
21
|
+
when '(' then ')' == ending
|
22
|
+
when '[' then ']' == ending
|
23
|
+
when '{' then '}' == ending
|
24
|
+
else
|
25
|
+
beginning[-1] == ending
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# SYNTACTIC VALIDITY
|
30
|
+
|
31
|
+
# I don't actually know if all of the error methods should set @has_error
|
32
|
+
# or just parse errors. I don't actually know how to produce the other errors O.o
|
33
|
+
#
|
34
|
+
# Here is what it is defining as of ruby-1.9.3-p125:
|
35
|
+
# on_alias_error
|
36
|
+
# on_assign_error
|
37
|
+
# on_class_name_error
|
38
|
+
# on_param_error
|
39
|
+
# on_parse_error
|
40
|
+
instance_methods.grep(/error/i).each do |error_meth|
|
41
|
+
super_meth = instance_method error_meth
|
42
|
+
define_method error_meth do |*args, &block|
|
17
43
|
@has_error = true
|
18
|
-
|
44
|
+
super_meth.bind(self).call(*args, &block)
|
19
45
|
end
|
20
|
-
|
21
|
-
end
|
46
|
+
end
|
22
47
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
end
|
48
|
+
def self.valid_ruby?(code)
|
49
|
+
parsed(code).valid_ruby?
|
50
|
+
end
|
27
51
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
instance
|
32
|
-
end
|
52
|
+
def valid_ruby?
|
53
|
+
!invalid_ruby?
|
54
|
+
end
|
33
55
|
|
34
|
-
|
35
|
-
|
36
|
-
|
56
|
+
def invalid_ruby?
|
57
|
+
@has_error || unclosed_string? || unclosed_regexp?
|
58
|
+
end
|
37
59
|
|
38
|
-
|
39
|
-
parsed(code.lines.to_a.last.to_s).has_comment?
|
40
|
-
end
|
60
|
+
# STRINGS
|
41
61
|
|
42
|
-
|
43
|
-
|
44
|
-
|
62
|
+
def self.unclosed_string?(code)
|
63
|
+
parsed(code).unclosed_string?
|
64
|
+
end
|
65
|
+
|
66
|
+
def string_opens
|
67
|
+
@string_opens ||= []
|
68
|
+
end
|
69
|
+
|
70
|
+
def on_tstring_beg(beginning)
|
71
|
+
string_opens.push beginning
|
72
|
+
super
|
73
|
+
end
|
74
|
+
|
75
|
+
def on_tstring_end(ending)
|
76
|
+
string_opens.pop if ends_match? string_opens.last, ending
|
77
|
+
super
|
78
|
+
end
|
79
|
+
|
80
|
+
def unclosed_string?
|
81
|
+
string_opens.any?
|
82
|
+
end
|
83
|
+
|
84
|
+
# REGEXPS
|
85
|
+
|
86
|
+
def self.unclosed_regexp?(code)
|
87
|
+
parsed(code).unclosed_regexp?
|
88
|
+
end
|
89
|
+
|
90
|
+
def regexp_opens
|
91
|
+
@regexp_opens ||= []
|
92
|
+
end
|
93
|
+
|
94
|
+
def on_regexp_beg(beginning)
|
95
|
+
regexp_opens.push beginning
|
96
|
+
super
|
97
|
+
end
|
98
|
+
|
99
|
+
def on_regexp_end(ending)
|
100
|
+
regexp_opens.pop if ends_match? regexp_opens.last, ending
|
101
|
+
super
|
102
|
+
end
|
103
|
+
|
104
|
+
def unclosed_regexp?
|
105
|
+
regexp_opens.any?
|
106
|
+
end
|
107
|
+
|
108
|
+
# COMMENTS
|
109
|
+
|
110
|
+
def self.ends_in_comment?(code)
|
111
|
+
parsed(code.lines.to_a.last.to_s).has_comment?
|
112
|
+
end
|
113
|
+
|
114
|
+
def has_comment?
|
115
|
+
@has_comment
|
116
|
+
end
|
117
|
+
|
118
|
+
def on_comment(*)
|
119
|
+
@has_comment = true
|
120
|
+
super
|
121
|
+
end
|
122
|
+
|
123
|
+
# RETURNS
|
45
124
|
|
46
|
-
|
47
|
-
|
125
|
+
# this is conspicuosuly inferior, but I can't figure out how to actually parse it
|
126
|
+
# see: http://www.ruby-forum.com/topic/4409633
|
127
|
+
def self.will_return?(code)
|
128
|
+
/(^|\s)return.*?\Z$/ =~ code
|
129
|
+
end
|
48
130
|
end
|
49
131
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# WARNING: DO NOT REQUIRE THIS FILE, IT WILL FUCK YOU UP!!!!!!
|
2
|
+
|
3
|
+
|
4
|
+
require 'stringio'
|
5
|
+
real_stdout = STDOUT
|
6
|
+
real_stderr = STDERR
|
7
|
+
STDOUT = $stdout = fake_stdout = StringIO.new
|
8
|
+
STDERR = $stderr = fake_stderr = StringIO.new
|
9
|
+
|
10
|
+
require 'yaml'
|
11
|
+
require 'seeing_is_believing/result'
|
12
|
+
$seeing_is_believing_current_result = SeeingIsBelieving::Result.new
|
13
|
+
|
14
|
+
at_exit do
|
15
|
+
$seeing_is_believing_current_result.stdout = fake_stdout.string
|
16
|
+
$seeing_is_believing_current_result.stderr = fake_stderr.string
|
17
|
+
|
18
|
+
real_stdout.write YAML.dump $seeing_is_believing_current_result
|
19
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class SeeingIsBelieving
|
2
|
+
module TracksLineNumbersSeen
|
3
|
+
INITIAL_LINE_NUMBER = 1 # uhm, should this change to 0?
|
4
|
+
|
5
|
+
def track_line_number(line_number)
|
6
|
+
@min_line_number = line_number if line_number < min_line_number
|
7
|
+
@max_line_number = line_number if line_number > max_line_number
|
8
|
+
end
|
9
|
+
|
10
|
+
def min_line_number
|
11
|
+
@min_line_number || INITIAL_LINE_NUMBER
|
12
|
+
end
|
13
|
+
|
14
|
+
def max_line_number
|
15
|
+
@max_line_number || INITIAL_LINE_NUMBER
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/seeing_is_believing.rb
CHANGED
@@ -1,22 +1,28 @@
|
|
1
1
|
require 'stringio'
|
2
|
+
require 'tmpdir'
|
2
3
|
|
3
4
|
require 'seeing_is_believing/result'
|
4
5
|
require 'seeing_is_believing/expression_list'
|
6
|
+
require 'seeing_is_believing/evaluate_by_moving_files'
|
5
7
|
|
6
8
|
# might not work on windows b/c of assumptions about line ends
|
7
9
|
class SeeingIsBelieving
|
8
|
-
|
9
|
-
|
10
|
-
|
10
|
+
include TracksLineNumbersSeen
|
11
|
+
BLANK_REGEX = /\A\s*\Z/
|
12
|
+
|
13
|
+
def initialize(string_or_stream, options={})
|
14
|
+
@string = string_or_stream
|
15
|
+
@stream = to_stream string_or_stream
|
16
|
+
@filename = options[:filename]
|
11
17
|
end
|
12
18
|
|
13
19
|
def call
|
14
20
|
@memoized_result ||= begin
|
15
21
|
program = ''
|
16
|
-
program << expression_list.call until
|
17
|
-
|
18
|
-
|
19
|
-
|
22
|
+
program << expression_list.call until eof? || data_segment?
|
23
|
+
program = record_exceptions_in program
|
24
|
+
program << "\n" << the_rest_of_the_stream if data_segment?
|
25
|
+
result_for program, min_line_number, max_line_number
|
20
26
|
end
|
21
27
|
end
|
22
28
|
|
@@ -25,11 +31,11 @@ class SeeingIsBelieving
|
|
25
31
|
attr_reader :stream
|
26
32
|
|
27
33
|
def expression_list
|
28
|
-
@expression_list ||= ExpressionList.new generator:
|
34
|
+
@expression_list ||= ExpressionList.new generator: lambda { get_next_line },
|
29
35
|
on_complete: lambda { |line, children, completions, line_number|
|
30
|
-
|
31
|
-
expression = [line, *children, *completions].join("\n")
|
32
|
-
if expression
|
36
|
+
track_line_number line_number
|
37
|
+
expression = [line, *children, *completions].map(&:chomp).join("\n")
|
38
|
+
if expression =~ BLANK_REGEX || SyntaxAnalyzer.ends_in_comment?(expression) || SyntaxAnalyzer.will_return?(expression)
|
33
39
|
expression + "\n"
|
34
40
|
else
|
35
41
|
record_yahself(expression, line_number) + "\n"
|
@@ -56,7 +62,42 @@ class SeeingIsBelieving
|
|
56
62
|
"end"
|
57
63
|
end
|
58
64
|
|
59
|
-
def
|
60
|
-
|
65
|
+
def result_for(program, min_line_number, max_line_number)
|
66
|
+
Dir.mktmpdir "seeing_is_believing_temp_dir" do |dir|
|
67
|
+
filename = @filename || File.join(dir, 'program.rb')
|
68
|
+
EvaluateByMovingFiles.new(program, filename).call.tap do |result|
|
69
|
+
result.track_line_number min_line_number
|
70
|
+
result.track_line_number max_line_number
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def eof?
|
76
|
+
peek_next_line.nil?
|
77
|
+
end
|
78
|
+
|
79
|
+
def data_segment?
|
80
|
+
peek_next_line == '__END__'
|
81
|
+
end
|
82
|
+
|
83
|
+
def peek_next_line
|
84
|
+
@next_line ||= begin
|
85
|
+
line = stream.gets
|
86
|
+
line && line.chomp
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def get_next_line
|
91
|
+
if @next_line
|
92
|
+
line = peek_next_line
|
93
|
+
@next_line = nil
|
94
|
+
line
|
95
|
+
else
|
96
|
+
peek_next_line && get_next_line
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def the_rest_of_the_stream
|
101
|
+
get_next_line << "\n" << stream.read
|
61
102
|
end
|
62
103
|
end
|
data/seeing_is_believing.gemspec
CHANGED
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'seeing_is_believing/evaluate_by_moving_files'
|
2
|
+
|
3
|
+
describe SeeingIsBelieving::EvaluateByMovingFiles do
|
4
|
+
let(:filedir) { File.expand_path '../../proving_grounds', __FILE__ }
|
5
|
+
let(:filename) { File.join filedir, 'some_filename' }
|
6
|
+
|
7
|
+
def invoke(program)
|
8
|
+
evaluator = described_class.new(program, filename)
|
9
|
+
FileUtils.rm_f evaluator.temp_filename
|
10
|
+
evaluator.call
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'evaluates the code when the file DNE' do
|
14
|
+
FileUtils.rm_f filename
|
15
|
+
invoke('print 1').stdout.should == '1'
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'evaluates the code when the file Exists' do
|
19
|
+
FileUtils.touch filename
|
20
|
+
invoke('print 1').stdout.should == '1'
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'raises an error when the temp file already exists' do
|
24
|
+
evaluator = described_class.new('', filename)
|
25
|
+
FileUtils.touch evaluator.temp_filename
|
26
|
+
expect { evaluator.call }.to raise_error SeeingIsBelieving::TempFileAlreadyExists
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'evaluates the code as the given file' do
|
30
|
+
invoke('print __FILE__').stdout.should == filename
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'does not change the original file' do
|
34
|
+
File.open(filename, 'w') { |f| f.write "ORIGINAL" }
|
35
|
+
invoke '1 + 1'
|
36
|
+
File.read(filename).should == "ORIGINAL"
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'uses HardCoreEnsure to move the file back' do
|
40
|
+
evaluator = described_class.new 'PROGRAM', filename, error_stream: StringIO.new
|
41
|
+
File.open(filename, 'w') { |f| f.write 'ORIGINAL' }
|
42
|
+
FileUtils.rm_rf evaluator.temp_filename
|
43
|
+
SeeingIsBelieving::HardCoreEnsure.should_receive(:call) do |options|
|
44
|
+
# initial state
|
45
|
+
File.exist?(evaluator.temp_filename).should == false
|
46
|
+
File.read(filename).should == 'ORIGINAL'
|
47
|
+
|
48
|
+
# after code
|
49
|
+
options[:code].call rescue nil
|
50
|
+
File.read(evaluator.temp_filename).should == 'ORIGINAL'
|
51
|
+
File.read(filename).should == 'PROGRAM'
|
52
|
+
|
53
|
+
# after ensure
|
54
|
+
options[:ensure].call
|
55
|
+
File.read(filename).should == 'ORIGINAL'
|
56
|
+
File.exist?(evaluator.temp_filename).should == false
|
57
|
+
end
|
58
|
+
evaluator.call
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'prints some error handling code to stderr if it fails' do
|
62
|
+
stderr = StringIO.new
|
63
|
+
evaluator = described_class.new 'raise "omg"', filename, error_stream: stderr
|
64
|
+
FileUtils.rm_f evaluator.temp_filename
|
65
|
+
expect { evaluator.call }.to raise_error
|
66
|
+
stderr.string.should include "It blew up"
|
67
|
+
end
|
68
|
+
end
|
@@ -105,11 +105,29 @@ describe SeeingIsBelieving::ExpressionList do
|
|
105
105
|
"end end"\
|
106
106
|
end
|
107
107
|
|
108
|
-
example
|
109
|
-
|
110
|
-
|
111
|
-
|
108
|
+
example 'example: multiline strings with valid code in them' do
|
109
|
+
block_invocations = 0
|
110
|
+
call ["'", "1", "'"] do |*expressions, line_number|
|
111
|
+
expressions.join('').should == "'1'"
|
112
|
+
line_number.should == 3
|
113
|
+
block_invocations += 1
|
114
|
+
end
|
115
|
+
block_invocations.should == 1
|
116
|
+
end
|
117
|
+
|
118
|
+
example 'example: multiline regexps with valid code in them' do
|
119
|
+
block_invocations = 0
|
120
|
+
call ['/', '1', '/'] do |*expressions, line_number|
|
121
|
+
expressions.join('').should == "/1/"
|
122
|
+
line_number.should == 3
|
123
|
+
block_invocations += 1
|
112
124
|
end
|
125
|
+
block_invocations.should == 1
|
126
|
+
end
|
127
|
+
|
128
|
+
example "example: smoke test debug option" do
|
129
|
+
stream = StringIO.new
|
130
|
+
call(%w[a+ b], debug: true, debug_stream: stream) { |*expressions, _| expressions.join("\n") }
|
113
131
|
stream.string.should include "GENERATED"
|
114
132
|
stream.string.should include "REDUCED"
|
115
133
|
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'ichannel'
|
2
|
+
require 'seeing_is_believing/hard_core_ensure'
|
3
|
+
|
4
|
+
describe SeeingIsBelieving::HardCoreEnsure do
|
5
|
+
def call(options)
|
6
|
+
described_class.new(options).call
|
7
|
+
end
|
8
|
+
|
9
|
+
it "raises an argument error if it doesn't get a code proc" do
|
10
|
+
expect { call ensure: -> {} }.to raise_error ArgumentError, "Must pass the :code key"
|
11
|
+
end
|
12
|
+
|
13
|
+
it "raises an argument error if it doesn't get an ensure proc" do
|
14
|
+
expect { call code: -> {} }.to raise_error ArgumentError, "Must pass the :ensure key"
|
15
|
+
end
|
16
|
+
|
17
|
+
it "raises an argument error if it gets any other keys" do
|
18
|
+
expect { call code: -> {}, ensure: -> {}, other: 123 }.to \
|
19
|
+
raise_error ArgumentError, "Unknown key: :other"
|
20
|
+
|
21
|
+
expect { call code: -> {}, ensure: -> {}, other1: 123, other2: 456 }.to \
|
22
|
+
raise_error ArgumentError, "Unknown keys: :other1, :other2"
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'invokes the code and returns the value' do
|
26
|
+
call(code: -> { :result }, ensure: -> {}).should == :result
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'invokes the ensure after the code' do
|
30
|
+
seen = []
|
31
|
+
call code: -> { seen << :code }, ensure: -> { seen << :ensure }
|
32
|
+
seen.should == [:code, :ensure]
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'invokes the ensure even if an exception is raised' do
|
36
|
+
ensure_invoked = false
|
37
|
+
expect do
|
38
|
+
call code: -> { raise Exception, 'omg!' }, ensure: -> { ensure_invoked = true }
|
39
|
+
end.to raise_error Exception, 'omg!'
|
40
|
+
ensure_invoked.should == true
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'invokes the code even if an interrupt is sent and there is a default handler' do
|
44
|
+
channel = IChannel.new Marshal
|
45
|
+
pid = fork do
|
46
|
+
old_handler = trap('INT') { channel.put "old handler invoked" }
|
47
|
+
call code: -> { sleep 0.1 }, ensure: -> { channel.put "ensure invoked" }
|
48
|
+
trap 'INT', old_handler
|
49
|
+
end
|
50
|
+
sleep 0.05
|
51
|
+
Process.kill 'INT', pid
|
52
|
+
Process.wait pid
|
53
|
+
channel.get.should == "ensure invoked"
|
54
|
+
channel.get.should == "old handler invoked"
|
55
|
+
channel.should_not be_readable
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'invokes the code even if an interrupt is sent and interrupts are set to ignore' do
|
59
|
+
channel = IChannel.new Marshal
|
60
|
+
pid = fork do
|
61
|
+
old_handler = trap 'INT', 'IGNORE'
|
62
|
+
result = call code: -> { sleep 0.1; 'code result' }, ensure: -> { channel.put "ensure invoked" }
|
63
|
+
channel.put result
|
64
|
+
trap 'INT', old_handler
|
65
|
+
end
|
66
|
+
sleep 0.05
|
67
|
+
Process.kill 'INT', pid
|
68
|
+
Process.wait pid
|
69
|
+
channel.get.should == "ensure invoked"
|
70
|
+
channel.get.should == 'code result'
|
71
|
+
channel.should_not be_readable
|
72
|
+
end
|
73
|
+
end
|