dia 1.5 → 2.0.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/{LICENSE → COPYING} +0 -0
- data/README.mkd +67 -0
- data/dia.gemspec +49 -0
- data/lib/dia.rb +11 -8
- data/lib/dia/application.rb +49 -0
- data/lib/dia/exception_struct.rb +7 -0
- data/lib/dia/exceptions.rb +5 -0
- data/lib/dia/functions.rb +8 -0
- data/lib/dia/profiles.rb +2 -6
- data/lib/dia/ruby_block.rb +319 -0
- data/lib/dia/shared_features.rb +91 -0
- data/test/setup.rb +5 -4
- data/test/suite/lib/dia/exception_struct.rb +40 -0
- data/test/suite/lib/dia/ruby_block.rb +561 -0
- data/test/suite/lib/dia/shared_features.rb +236 -0
- metadata +31 -42
- data/.yardopts +0 -4
- data/HACKING.md +0 -13
- data/NEWS.md +0 -61
- data/README.md +0 -170
- data/TODO.md +0 -8
- data/lib/dia/commonapi.rb +0 -7
- data/lib/dia/sandbox.rb +0 -208
- data/test/suite/check_if_sandbox_is_alive_test.rb +0 -29
- data/test/suite/exception()_test.rb +0 -55
- data/test/suite/exception_raised?_test.rb +0 -22
- data/test/suite/exceptions_test.rb +0 -12
- data/test/suite/exit_status_test.rb +0 -16
- data/test/suite/passing_parameters_to_constructer_test.rb +0 -34
- data/test/suite/run_block_in_sandbox_test.rb +0 -132
- data/test/suite/terminate_sandbox_test.rb +0 -29
data/{LICENSE → COPYING}
RENAMED
File without changes
|
data/README.mkd
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
# Dia
|
2
|
+
Through the use of technology found on Apple's Leopard and Snow Leopard
|
3
|
+
operating systems, Dia can create dynamic and robust sandbox environments
|
4
|
+
for applications and for blocks of ruby code. The Ruby API was designed to be
|
5
|
+
simple, and a joy to use. I hope you feel the same way :-)
|
6
|
+
|
7
|
+
## Quick Example
|
8
|
+
|
9
|
+
* RubyBlock
|
10
|
+
|
11
|
+
require('rubygems')
|
12
|
+
require('dia')
|
13
|
+
require('open-uri')
|
14
|
+
|
15
|
+
sandbox = Dia::RubyBlock.new(Dia::Profiles::NO_INTERNET) do
|
16
|
+
open('http://www.google.com')
|
17
|
+
end
|
18
|
+
|
19
|
+
sandbox.rescue_exception = true
|
20
|
+
sandbox.run
|
21
|
+
puts "Exception : #{sandbox.exception.klass}"
|
22
|
+
puts "Message : #{sandbox.exception.message}"
|
23
|
+
|
24
|
+
* Application
|
25
|
+
|
26
|
+
require('rubygems')
|
27
|
+
require('dia')
|
28
|
+
|
29
|
+
sandbox = Dia::Application.new(Dia::Profiles::NO_INTERNET,
|
30
|
+
'/path/to/firefox')
|
31
|
+
|
32
|
+
sandbox.run_nonblock
|
33
|
+
sandbox.terminate
|
34
|
+
|
35
|
+
## Documentation
|
36
|
+
|
37
|
+
* [API Documentation](http://yardoc.org/docs/robgleeson-Dia/)
|
38
|
+
Written using YARD, the API documentation makes a great reference.
|
39
|
+
*The API documentation linked is for the latest stable release*
|
40
|
+
|
41
|
+
* [Mailing list](http://groups.google.com/group/ruby-dia)
|
42
|
+
Troubleshoot your problems with other Dia users on the Google Groups mailing list.
|
43
|
+
|
44
|
+
* Wiki documentation
|
45
|
+
*Work in progress*
|
46
|
+
|
47
|
+
## Supported Rubies.
|
48
|
+
|
49
|
+
The following Ruby implementations have had the test suite run against them, and
|
50
|
+
reported a 100% success rate.
|
51
|
+
|
52
|
+
* MRI
|
53
|
+
* 1.8.7-p299
|
54
|
+
* 1.9.1-p378
|
55
|
+
* 1.9.2-rc1
|
56
|
+
* REE
|
57
|
+
* Ruby Enterprise Edition 2010.02 (1.8.7-p253)
|
58
|
+
|
59
|
+
MacRuby is not supported because it does not support Kernel.fork, and it won't add support
|
60
|
+
for fork anytime soon(if ever).
|
61
|
+
JRuby has experimental support for fork, but I haven't tried it.
|
62
|
+
|
63
|
+
## Bugs
|
64
|
+
Bug reports are _very_ welcome, and can be reported through the
|
65
|
+
[issue tracker](http://github.com/robgleeson/dia/issues).
|
66
|
+
|
67
|
+
|
data/dia.gemspec
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require('./lib/dia')
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
|
6
|
+
s.name = %q{dia}
|
7
|
+
s.version = Dia::VERSION
|
8
|
+
s.authors = ["Robert Gleeson"]
|
9
|
+
s.email = %q{rob@flowof.info}
|
10
|
+
s.date = Time.now.strftime("%Y-%m-%d")
|
11
|
+
|
12
|
+
s.description = %q{Through the use of technology found on Apple's Leopard and Snow Leopard
|
13
|
+
operating systems, Dia can create dynamic and robust sandbox environments
|
14
|
+
for applications and for blocks of ruby code.
|
15
|
+
The Ruby API was designed to be simple, and a joy to use.
|
16
|
+
I hope you feel the same way :-)}
|
17
|
+
|
18
|
+
s.summary = %q{Through the use of technology found on Apple's Leopard and Snow Leopard
|
19
|
+
operating systems, Dia can create dynamic and robust sandbox environments
|
20
|
+
for applications and for blocks of ruby code.
|
21
|
+
The Ruby API was designed to be simple, and a joy to use.
|
22
|
+
I hope you feel the same way :-)}
|
23
|
+
|
24
|
+
|
25
|
+
s.require_paths = ["lib"]
|
26
|
+
s.files = Dir["lib/**/*.rb"] + Dir["test/**/*.rb"] + %w(COPYING README.mkd dia.gemspec)
|
27
|
+
s.test_files = Dir["test/**/*.rb"]
|
28
|
+
|
29
|
+
|
30
|
+
s.add_runtime_dependency(%q<ffi>, ["= 0.6.2"])
|
31
|
+
s.add_development_dependency(%q<yard>, [">= 0"])
|
32
|
+
s.has_rdoc = %q{yard}
|
33
|
+
|
34
|
+
s.post_install_message = <<-MESSAGE
|
35
|
+
--------------------------------------------------------------------
|
36
|
+
Dia (#{Dia::VERSION})
|
37
|
+
|
38
|
+
Thanks for installing Dia, #{Dia::VERSION}!
|
39
|
+
|
40
|
+
>=2.0.0 releases include public API changes that are not backward
|
41
|
+
compatiable with older releases. Be sure to check the docs!
|
42
|
+
|
43
|
+
[Github] http://github.com/robgleeson/dia
|
44
|
+
[API Documentation] http://yardoc.org/docs/robgleeson-dia/
|
45
|
+
[Mailing List (new)] http://groups.google.com/group/ruby-dia
|
46
|
+
--------------------------------------------------------------------
|
47
|
+
MESSAGE
|
48
|
+
|
49
|
+
end
|
data/lib/dia.rb
CHANGED
@@ -1,11 +1,14 @@
|
|
1
|
-
gem
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
1
|
+
gem('ffi', '0.6.2')
|
2
|
+
require('ffi')
|
3
|
+
require('ostruct')
|
4
|
+
require(File.expand_path('dia/shared_features' , File.dirname(__FILE__)))
|
5
|
+
require(File.expand_path('dia/functions' , File.dirname(__FILE__)))
|
6
|
+
require(File.expand_path('dia/profiles' , File.dirname(__FILE__)))
|
7
|
+
require(File.expand_path('dia/ruby_block' , File.dirname(__FILE__)))
|
8
|
+
require(File.expand_path('dia/application' , File.dirname(__FILE__)))
|
9
|
+
require(File.expand_path('dia/exceptions' , File.dirname(__FILE__)))
|
10
|
+
require(File.expand_path('dia/exception_struct' , File.dirname(__FILE__)))
|
6
11
|
|
7
12
|
module Dia
|
8
|
-
VERSION = '
|
9
|
-
class SandboxException < StandardError; end
|
13
|
+
VERSION = '2.0.0'
|
10
14
|
end
|
11
|
-
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Dia
|
2
|
+
|
3
|
+
class Application
|
4
|
+
include Dia::SharedFeatures
|
5
|
+
|
6
|
+
|
7
|
+
# @param [String] Profile Accepts one of five profiles found under the {Dia::Profiles}
|
8
|
+
# module.
|
9
|
+
#
|
10
|
+
# @param [String] Application Accepts a path to an application.
|
11
|
+
#
|
12
|
+
# @raise [ArgumentError] It may raise an ArgumentError if it isn't possible to use a
|
13
|
+
# particular profile with this type of sandbox.
|
14
|
+
def initialize(profile, app)
|
15
|
+
@profile = profile
|
16
|
+
@app = app
|
17
|
+
raise(ArgumentError, "Dia::Profiles::NO_OS_SERVICES is not applicable to the Application " \
|
18
|
+
"sandbox.") if Dia::Profiles::NO_OS_SERVICES == @profile
|
19
|
+
end
|
20
|
+
|
21
|
+
# This method will spawn a child process, initialize a sandbox environment, and call exec()
|
22
|
+
# to start your application.
|
23
|
+
#
|
24
|
+
# @return [Fixnum] Returns the Process ID(PID) of the child process used to execute an
|
25
|
+
# application in a sandbox.
|
26
|
+
def run
|
27
|
+
@pid = fork do
|
28
|
+
initialize_sandbox
|
29
|
+
exec(@app)
|
30
|
+
end
|
31
|
+
|
32
|
+
_, @exit_status = Process.wait2(@pid)
|
33
|
+
@pid
|
34
|
+
end
|
35
|
+
|
36
|
+
# An identical but non-blocking form of {#run}.
|
37
|
+
def run_nonblock
|
38
|
+
@pid = fork do
|
39
|
+
initialize_sandbox
|
40
|
+
exec(@app)
|
41
|
+
end
|
42
|
+
|
43
|
+
@exit_status = Process.detach(@pid)
|
44
|
+
@pid
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
@@ -0,0 +1,7 @@
|
|
1
|
+
module Dia
|
2
|
+
# @attr_reader [String] klass Returns Exception#class as a String.
|
3
|
+
# @attr_reader [String] message Returns Exception#message as a String.
|
4
|
+
# @attr_reader [String] backtrace Returns Exception#backtrace as a String.
|
5
|
+
class ExceptionStruct < Struct.new(:klass, :message, :backtrace)
|
6
|
+
end
|
7
|
+
end
|
data/lib/dia/profiles.rb
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
module Dia
|
2
|
-
|
3
2
|
module Profiles
|
4
|
-
extend
|
5
|
-
ffi_lib(%w(
|
3
|
+
extend(FFI::Library)
|
4
|
+
ffi_lib(%w(system))
|
6
5
|
|
7
6
|
NO_INTERNET = attach_variable(:kSBXProfileNoInternet,
|
8
7
|
:string).read_string
|
@@ -14,8 +13,5 @@ module Dia
|
|
14
13
|
:string).read_string
|
15
14
|
NO_OS_SERVICES = attach_variable(:kSBXProfilePureComputation,
|
16
15
|
:string).read_string
|
17
|
-
|
18
16
|
end
|
19
|
-
|
20
17
|
end
|
21
|
-
|
@@ -0,0 +1,319 @@
|
|
1
|
+
module Dia
|
2
|
+
|
3
|
+
class RubyBlock
|
4
|
+
|
5
|
+
require('io/wait')
|
6
|
+
require('stringio')
|
7
|
+
include Dia::SharedFeatures
|
8
|
+
|
9
|
+
|
10
|
+
# @param [String] Profile Accepts one of five profiles which can be found
|
11
|
+
# under the {Dia::Profiles} module.
|
12
|
+
#
|
13
|
+
# @param [Proc] Block Accepts a block or Proc object as its second argument.
|
14
|
+
#
|
15
|
+
# @raise [ArgumentError] It will raise an ArgumentError if a profile and block
|
16
|
+
# isn't supplied to the constructor
|
17
|
+
#
|
18
|
+
# @return [Dia::RubyBlock] Returns an instance of Dia::RubyBlock.
|
19
|
+
def initialize(profile, &block)
|
20
|
+
raise(ArgumentError, "It is required that a block be passed to the constructor.\n" \
|
21
|
+
"Please consult the documentation.") unless block_given?
|
22
|
+
@profile = profile
|
23
|
+
@proc = block
|
24
|
+
@rescue = false
|
25
|
+
@redirect_stdout = false
|
26
|
+
@redirect_stderr = false
|
27
|
+
@pipes = {}
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
# When the "capture stdout" feature is enabled, this method will return the contents
|
32
|
+
# of the standard output stream for the child process used to execute your sandbox.
|
33
|
+
#
|
34
|
+
# @return [String, nil] Returns the contents of stdout.
|
35
|
+
# Returns nil when no data is available on stdout, or when the
|
36
|
+
# "capture stdout" feature is disabled.
|
37
|
+
#
|
38
|
+
# @see #redirect_stdout= This feature is disabled by default. See how to enable it.
|
39
|
+
#
|
40
|
+
def stdout
|
41
|
+
if pipes_readable?(@pipes[:stdout_reader], @pipes[:stdout_writer])
|
42
|
+
@pipes[:stdout_writer].close
|
43
|
+
@stdout = @pipes[:stdout_reader].read
|
44
|
+
@pipes[:stdout_reader].close
|
45
|
+
end
|
46
|
+
@stdout
|
47
|
+
end
|
48
|
+
|
49
|
+
# This method can enable or disable a feature that will capture standard output
|
50
|
+
# in the child process that is spawned to execute a sandbox.
|
51
|
+
#
|
52
|
+
# @param [Boolean] Boolean Accepts a true(-ish) or false(-ish) value.
|
53
|
+
# @return [Boolean] Returns the calling argument.
|
54
|
+
# @see #stdout See #stdout for accessing the contents of stdout.
|
55
|
+
def redirect_stdout=(boolean)
|
56
|
+
@redirect_stdout = boolean
|
57
|
+
end
|
58
|
+
|
59
|
+
# This method will tell you if standatd output is being redirected in the child
|
60
|
+
# process used to execute your sandbox.
|
61
|
+
#
|
62
|
+
# @see #redirect_stdout= See how to enable the "redirect stdout" feature.
|
63
|
+
#
|
64
|
+
# @return [Boolean] Returns true or false.
|
65
|
+
def redirect_stdout?
|
66
|
+
!!@rescue_stdout
|
67
|
+
end
|
68
|
+
|
69
|
+
# This method will tell you if standard error is being redirected in the child process
|
70
|
+
# used to execute your sandbox.
|
71
|
+
#
|
72
|
+
# @see #redirect_stderr= See how to enable the "redirect stderr" feature.
|
73
|
+
# @return [Boolean] returns true or false.
|
74
|
+
def redirect_stderr?
|
75
|
+
!!@rescue_stderr
|
76
|
+
end
|
77
|
+
|
78
|
+
# This method can enable or disable a feature that will capture standard error output
|
79
|
+
# in the child process that is spawned to execute a sandbox.
|
80
|
+
#
|
81
|
+
# @param [Boolean] Boolean Accepts a true(-ish) or false(-ish) value.
|
82
|
+
# @return [Boolean] Returns the calling argument.
|
83
|
+
# @see #stdout See #stderr for accessing the contents of stderr.
|
84
|
+
def redirect_stderr=(boolean)
|
85
|
+
@redirect_stderr = boolean
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
# When the "capture stderr" feature is enabled, this method will return the contents
|
90
|
+
# of the standard error stream for the child process used to execute your sandbox.
|
91
|
+
#
|
92
|
+
# @return [String, nil] Returns the contents of stderr.
|
93
|
+
# Returns nil when no data is available on stderr, or when the
|
94
|
+
# "capture stderr" feature is disabled.
|
95
|
+
#
|
96
|
+
# @see #redirect_stderr= This feature is disabled by default. See how to enable it.
|
97
|
+
#
|
98
|
+
def stderr
|
99
|
+
if pipes_readable?(@pipes[:stderr_reader], @pipes[:stderr_writer])
|
100
|
+
@pipes[:stderr_writer].close
|
101
|
+
@stderr = @pipes[:stderr_reader].read
|
102
|
+
@pipes[:stderr_reader].close
|
103
|
+
end
|
104
|
+
@stderr
|
105
|
+
end
|
106
|
+
|
107
|
+
# This method will tell you if an exception has been raised in the child process
|
108
|
+
# used to execute your sandbox.
|
109
|
+
# The "capture exception" feature must be enabled for this method to ever
|
110
|
+
# return true.
|
111
|
+
#
|
112
|
+
# @see #rescue_exception= See #rescue_exception= for enabling the capture
|
113
|
+
# of raised exceptions in your sandbox.
|
114
|
+
#
|
115
|
+
# @see #exception See the #exception method for accessing an
|
116
|
+
# exception raised in your sandbox.
|
117
|
+
#
|
118
|
+
# @return [Boolean] Returns true or false.
|
119
|
+
def exception_raised?
|
120
|
+
!!exception
|
121
|
+
end
|
122
|
+
|
123
|
+
# This method will tell you if the {#rescue_exception=} feature is enabled by
|
124
|
+
# returning a boolean.
|
125
|
+
#
|
126
|
+
# @see #rescue_exception= See #rescue_exception= for enabling the capture
|
127
|
+
# of raised exceptions in your sandbox.
|
128
|
+
#
|
129
|
+
# @see #exception See the #exception method for accessing an
|
130
|
+
# exception raised in your sandbox.
|
131
|
+
#
|
132
|
+
# @return [Boolean] Returns true or false.
|
133
|
+
# @since 2.0.0
|
134
|
+
def rescue_exception?
|
135
|
+
!!@rescue
|
136
|
+
end
|
137
|
+
|
138
|
+
# This method can enable or disable a feature that will try to capture
|
139
|
+
# raised exceptions in the child process that is spawned to execute a sandbox.
|
140
|
+
#
|
141
|
+
# @param [Boolean] Boolean Accepts a true(-ish) or false(-ish) value.
|
142
|
+
#
|
143
|
+
# @return [Boolean] Returns the calling argument.
|
144
|
+
#
|
145
|
+
# @see #exception See #exception for information on how to access
|
146
|
+
# the data of an exception raised in your sandbox.
|
147
|
+
#
|
148
|
+
# @since 2.0.0
|
149
|
+
def rescue_exception=(boolean)
|
150
|
+
@rescue = boolean
|
151
|
+
end
|
152
|
+
|
153
|
+
# When the "capture exceptions" feature is enabled and an exception has been raised in
|
154
|
+
# the child process used to execute your sandbox, this method will return a subclass
|
155
|
+
# of Struct whose attributes represent the exception data.
|
156
|
+
#
|
157
|
+
# Every call {#run} or {#run_nonblock} will reset the instance variable referencing the
|
158
|
+
# object storing exception data to nil.
|
159
|
+
#
|
160
|
+
# @return [Dia::ExceptionStruct, nil] Returns an instance of {Dia::ExceptionStruct} or nil
|
161
|
+
# when there is no exception available.
|
162
|
+
#
|
163
|
+
# @see #rescue_exception= The "capture exception" feature is disabled by default.
|
164
|
+
# See how to enable it.
|
165
|
+
#
|
166
|
+
# @see Dia::ExceptionStruct The documentation for Dia::ExceptionStruct.
|
167
|
+
#
|
168
|
+
# @since 1.5
|
169
|
+
def exception
|
170
|
+
if pipes_readable?(@pipes[:exception_reader], @pipes[:exception_writer])
|
171
|
+
@pipes[:exception_writer].close
|
172
|
+
@e = ExceptionStruct.new *Marshal.load(@pipes[:exception_reader].read).values_at(:klass,
|
173
|
+
:message,
|
174
|
+
:backtrace)
|
175
|
+
@pipes[:exception_reader].close
|
176
|
+
end
|
177
|
+
@e
|
178
|
+
end
|
179
|
+
|
180
|
+
# The run method will spawn a child process to execute the block supplied to the constructer
|
181
|
+
# in a sandbox.
|
182
|
+
# This method will block. See {#run_nonblock} for the non-blocking form of
|
183
|
+
# this method.
|
184
|
+
#
|
185
|
+
# @param [Arguments] Arguments A variable amount of arguments that will
|
186
|
+
# be passed onto the block supplied to the
|
187
|
+
# constructer. Optional.
|
188
|
+
#
|
189
|
+
# @raise [SystemCallError] It may raise a number of subclasses of SystemCallError
|
190
|
+
# in a child process if a sandbox violates imposed
|
191
|
+
# restrictions.
|
192
|
+
#
|
193
|
+
# @raise [Dia::SandboxException] It may raise
|
194
|
+
# {Dia::Exceptions::SandboxException}
|
195
|
+
# in a child process if it was not possible
|
196
|
+
# to initialize a sandbox environment.
|
197
|
+
#
|
198
|
+
# @return [Fixnum] The Process ID(PID) that the sandbox has
|
199
|
+
# been launched under.
|
200
|
+
def run(*args)
|
201
|
+
launch(*args)
|
202
|
+
|
203
|
+
# parent ..
|
204
|
+
_, @exit_status = Process.wait2(@pid)
|
205
|
+
@pid
|
206
|
+
end
|
207
|
+
|
208
|
+
# An identical, but non-blocking form of {#run}.
|
209
|
+
def run_nonblock(*args)
|
210
|
+
launch(*args)
|
211
|
+
|
212
|
+
@exit_status = Process.detach(@pid)
|
213
|
+
@pid
|
214
|
+
end
|
215
|
+
|
216
|
+
private
|
217
|
+
# @api private
|
218
|
+
def launch(*args)
|
219
|
+
@e = @stdout = nil
|
220
|
+
close_pipes_if_needed
|
221
|
+
open_pipes_if_needed
|
222
|
+
|
223
|
+
@pid = fork do
|
224
|
+
redirect(:stdout) if @redirect_stdout
|
225
|
+
redirect(:stderr) if @redirect_stderr
|
226
|
+
if @rescue
|
227
|
+
begin
|
228
|
+
initialize_sandbox
|
229
|
+
@proc.call(*args)
|
230
|
+
rescue SystemExit, SignalException, NoMemoryError => e
|
231
|
+
raise(e)
|
232
|
+
rescue Exception => e
|
233
|
+
begin
|
234
|
+
write_exception(e)
|
235
|
+
rescue SystemExit, SignalException, NoMemoryError => e
|
236
|
+
raise(e)
|
237
|
+
rescue Exception => e
|
238
|
+
write_exception(e)
|
239
|
+
end
|
240
|
+
ensure
|
241
|
+
write_stdout_and_stderr_if_needed
|
242
|
+
close_pipes_if_needed
|
243
|
+
end
|
244
|
+
else
|
245
|
+
begin
|
246
|
+
initialize_sandbox
|
247
|
+
@proc.call(*args)
|
248
|
+
ensure
|
249
|
+
write_stdout_and_stderr_if_needed
|
250
|
+
close_pipes_if_needed
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
# @api private
|
257
|
+
def write_stdout_and_stderr_if_needed
|
258
|
+
if @redirect_stdout
|
259
|
+
$stdout.rewind
|
260
|
+
@pipes[:stdout_reader].close
|
261
|
+
@pipes[:stdout_writer].write($stdout.read)
|
262
|
+
@pipes[:stdout_writer].close
|
263
|
+
end
|
264
|
+
|
265
|
+
if @redirect_stderr
|
266
|
+
$stderr.rewind
|
267
|
+
@pipes[:stderr_reader].close
|
268
|
+
@pipes[:stderr_writer].write($stderr.read)
|
269
|
+
@pipes[:stderr_writer].close
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
# @api private
|
274
|
+
def close_pipes_if_needed
|
275
|
+
@pipes.each do |key, pipe|
|
276
|
+
if !pipe.nil? && !pipe.closed?
|
277
|
+
pipe.close
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
# @api private
|
283
|
+
def open_pipes_if_needed
|
284
|
+
@pipes[:exception_reader], @pipes[:exception_writer] = IO.pipe if @rescue
|
285
|
+
@pipes[:stdout_reader] , @pipes[:stdout_writer] = IO.pipe if @redirect_stdout
|
286
|
+
@pipes[:stderr_reader] , @pipes[:stderr_writer] = IO.pipe if @redirect_stderr
|
287
|
+
end
|
288
|
+
|
289
|
+
# @api private
|
290
|
+
def write_exception(e)
|
291
|
+
@pipes[:exception_writer].write(Marshal.dump({ :klass => e.class.to_s ,
|
292
|
+
:backtrace => e.backtrace.join("\n"),
|
293
|
+
:message => e.message.to_s }) )
|
294
|
+
end
|
295
|
+
|
296
|
+
# @api private
|
297
|
+
def pipes_readable?(reader, writer)
|
298
|
+
(reader && writer) &&
|
299
|
+
(!reader.closed? && !writer.closed?) &&
|
300
|
+
(reader.ready?)
|
301
|
+
end
|
302
|
+
|
303
|
+
# @api private
|
304
|
+
def redirect(symbol)
|
305
|
+
level = $VERBOSE
|
306
|
+
$VERBOSE = nil
|
307
|
+
if symbol == :stdout
|
308
|
+
$stdout = StringIO.new
|
309
|
+
Object.const_set(:STDOUT, $stdout)
|
310
|
+
else
|
311
|
+
$stderr = StringIO.new
|
312
|
+
Object.const_set(:STDERR, $stderr)
|
313
|
+
end
|
314
|
+
$VERBOSE = level
|
315
|
+
end
|
316
|
+
|
317
|
+
end
|
318
|
+
|
319
|
+
end
|