4 from rox
import g
, saving
8 class ChildError(Exception):
9 "Raised when the child process reports an error."
11 class ChildKilled(saving
.AbortSave
):
12 "Raised when child died due to calling the kill method."
14 saving
.AbortSave
.__init
__(self
, "Operation aborted at user's request")
17 """Return text with \ and ' escaped"""
18 return text
.replace("\\", "\\\\").replace("'", "\\'")
20 def Tmp(mode
= 'w+b'):
21 "Create a seekable, randomly named temp file (deleted automatically after use)."
24 name
= tempfile
.mktemp(`random
.randint(1, 1000000)`
+ '-archive')
26 fd
= os
.open(name
, os
.O_RDWR|os
.O_CREAT|os
.O_EXCL
, 0700)
27 tmp
= tempfile
.TemporaryFileWrapper(os
.fdopen(fd
, mode
), name
)
31 class PipeThroughCommand
:
32 def __init__(self
, command
, src
, dst
):
33 """Execute 'command' with src as stdin and writing to stream
34 dst. src must be a fileno() stream, but dst need not be.
35 Either stream may be None if input or output is not required.
36 Call the wait() method to wait for the command to finish."""
38 assert src
is None or hasattr(src
, 'fileno')
40 # Output to 'dst' directly if it's a fileno stream. Otherwise,
41 # send output to a temporary file.
43 if hasattr(dst
, 'fileno'):
48 fd
= tmp_stream
.fileno()
52 # Create a pipe to collect stderr from child
53 stderr_r
, stderr_w
= os
.pipe()
62 # This is the child process
64 os
.setpgid(0, 0) # Start a new process group
69 os
.dup2(src
.fileno(), 0)
72 if os
.system(command
) == 0:
73 os
._exit
(0) # No error code or signal
78 # This is the parent process
80 self
.err_from_child
= stderr_r
82 self
.command
= command
87 if dst
and tmp_stream
is not dst
:
88 self
.tmp_stream
= tmp_stream
90 self
.tmp_stream
= None
93 self
.tag
= g
.input_add_full(self
.err_from_child
,
94 g
.gdk
.INPUT_READ
, self
.got_errors
)
96 def got_errors(self
, source
, cond
):
97 got
= os
.read(self
.err_from_child
, 100)
102 g
.input_remove(self
.tag
)
105 errors
= self
.errors
.strip()
108 pid
, status
= os
.waitpid(self
.child
, 0)
116 err
= ChildError("Errors from command '%s':\n%s" % (self
.command
, errors
))
118 err
= ChildError("Command '%s' returned an error code!" % self
.command
)
120 # If dst wasn't a fileno stream, copy from the temp file to it
121 if not err
and self
.tmp_stream
:
122 self
.tmp_stream
.seek(0)
123 self
.dst
.write(self
.tmp_stream
.read())
128 """Run a recursive mainloop until the command terminates.
129 Raises an exception on error."""
131 def set_done(exception
):
132 done
.append(exception
)
134 self
.callback
= set_done
142 assert self
.child
!= -1
145 os
.kill(-self
.child
, signal
.SIGTERM
)
148 "Check that this module works."
151 error
= sys
.exc_info()[1]
152 print "(error reported was '%s')" % error
154 def pipe_through_command(command
, src
, dst
): PipeThroughCommand(command
, src
, dst
).wait()
156 print "Test escape()..."
158 assert escape(''' a test ''') == ' a test '
159 assert escape(''' "a's test" ''') == ''' "a\\'s test" '''
160 assert escape(''' "a\\'s test" ''') == ''' "a\\\\\\'s test" '''
162 print "Test Tmp()..."
168 os
.write(file.fileno(), 'World')
171 assert file.read() == 'Hello World'
173 print "Test pipe_through_command():"
175 print "Try an invalid command..."
177 pipe_through_command('bad_command_1234', None, None)
184 print "Try a valid command..."
185 pipe_through_command('exit 0', None, None)
187 print "Writing to a non-fileno stream..."
188 from cStringIO
import StringIO
190 pipe_through_command('echo Hello', None, a
)
191 assert a
.getvalue() == 'Hello\n'
193 print "Reading from a stream to a StringIO..."
195 pipe_through_command('cat', file, a
)
196 assert a
.getvalue() == 'Hello\nello World'
198 print "Writing to a fileno stream..."
201 pipe_through_command('echo Foo', None, file)
203 assert file.read() == 'Foo\n'
205 print "Read and write fileno streams..."
211 pipe_through_command('cat', src
, file)
213 assert file.read() == '123'
215 print "Detect non-zero exit value..."
217 pipe_through_command('exit 1', None, None)
223 print "Detect writes to stderr..."
225 pipe_through_command('echo one >&2; sleep 2; echo two >&2', None, None)
231 print "Check tmp file is deleted..."
233 assert os
.path
.exists(name
)
235 assert not os
.path
.exists(name
)
237 print "Check we can kill a runaway proces..."
238 ptc
= PipeThroughCommand('sleep 100; exit 1', None, None)
241 g
.timeout_add(2000, stop
)
248 print "All tests passed!"
250 if __name__
== '__main__':