seeing_is_believing 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|