@@ -256,48 +256,121 @@ def tearDown(self):
256256
257257class SiginterruptTest (unittest .TestCase ):
258258 signum = signal .SIGUSR1
259- def readpipe_interrupted (self , cb ):
259+
260+ def setUp (self ):
261+ """Install a no-op signal handler that can be set to allow
262+ interrupts or not, and arrange for the original signal handler to be
263+ re-installed when the test is finished.
264+ """
265+ self ._cleanups = []
266+ oldhandler = signal .signal (self .signum , lambda x ,y : None )
267+ self .addCleanup (signal .signal , self .signum , oldhandler )
268+
269+
270+ def tearDown (self ):
271+ """Run any cleanup functions which have been registered.
272+ """
273+ for (f , a ) in self ._cleanups :
274+ f (* a )
275+
276+
277+ def addCleanup (self , f , * a ):
278+ """Register a function to be called at the end of the test method
279+ run.
280+ """
281+ self ._cleanups .append ((f , a ))
282+
283+
284+ def readpipe_interrupted (self ):
285+ """Perform a read during which a signal will arrive. Return True if the
286+ read is interrupted by the signal and raises an exception. Return False
287+ if it returns normally.
288+ """
289+ # Create a pipe that can be used for the read. Also clean it up
290+ # when the test is over, since nothing else will (but see below for
291+ # the write end).
260292 r , w = os .pipe ()
293+ self .addCleanup (os .close , r )
294+
295+ # Create another process which can send a signal to this one to try
296+ # to interrupt the read.
261297 ppid = os .getpid ()
262298 pid = os .fork ()
263299
264- oldhandler = signal .signal (self .signum , lambda x ,y : None )
265- cb ()
266- if pid == 0 :
267- # child code: sleep, kill, sleep. and then exit,
268- # which closes the pipe from which the parent process reads
300+ if pid == 0 :
301+ # Child code: sleep to give the parent enough time to enter the
302+ # read() call (there's a race here, but it's really tricky to
303+ # eliminate it); then signal the parent process. Also, sleep
304+ # again to make it likely that the signal is delivered to the
305+ # parent process before the child exits. If the child exits
306+ # first, the write end of the pipe will be closed and the test
307+ # is invalid.
269308 try :
270309 time .sleep (0.2 )
271310 os .kill (ppid , self .signum )
272311 time .sleep (0.2 )
273312 finally :
313+ # No matter what, just exit as fast as possible now.
274314 exit_subprocess ()
275-
276- try :
315+ else :
316+ # Parent code.
317+ # Make sure the child is eventually reaped, else it'll be a
318+ # zombie for the rest of the test suite run.
319+ self .addCleanup (os .waitpid , pid , 0 )
320+
321+ # Close the write end of the pipe. The child has a copy, so
322+ # it's not really closed until the child exits. We need it to
323+ # close when the child exits so that in the non-interrupt case
324+ # the read eventually completes, otherwise we could just close
325+ # it *after* the test.
277326 os .close (w )
278327
328+ # Try the read and report whether it is interrupted or not to
329+ # the caller.
279330 try :
280- d = os .read (r , 1 )
331+ d = os .read (r , 1 )
281332 return False
282333 except OSError , err :
283334 if err .errno != errno .EINTR :
284335 raise
285336 return True
286- finally :
287- signal .signal (self .signum , oldhandler )
288- os .waitpid (pid , 0 )
289337
290338 def test_without_siginterrupt (self ):
291- i = self .readpipe_interrupted (lambda : None )
292- self .assertEquals (i , True )
339+ """If a signal handler is installed and siginterrupt is not called
340+ at all, when that signal arrives, it interrupts a syscall that's in
341+ progress.
342+ """
343+ i = self .readpipe_interrupted ()
344+ self .assertTrue (i )
345+ # Arrival of the signal shouldn't have changed anything.
346+ i = self .readpipe_interrupted ()
347+ self .assertTrue (i )
293348
294349 def test_siginterrupt_on (self ):
295- i = self .readpipe_interrupted (lambda : signal .siginterrupt (self .signum , 1 ))
296- self .assertEquals (i , True )
350+ """If a signal handler is installed and siginterrupt is called with
351+ a true value for the second argument, when that signal arrives, it
352+ interrupts a syscall that's in progress.
353+ """
354+ signal .siginterrupt (self .signum , 1 )
355+ i = self .readpipe_interrupted ()
356+ self .assertTrue (i )
357+ # Arrival of the signal shouldn't have changed anything.
358+ i = self .readpipe_interrupted ()
359+ self .assertTrue (i )
297360
298361 def test_siginterrupt_off (self ):
299- i = self .readpipe_interrupted (lambda : signal .siginterrupt (self .signum , 0 ))
300- self .assertEquals (i , False )
362+ """If a signal handler is installed and siginterrupt is called with
363+ a false value for the second argument, when that signal arrives, it
364+ does not interrupt a syscall that's in progress.
365+ """
366+ signal .siginterrupt (self .signum , 0 )
367+ i = self .readpipe_interrupted ()
368+ self .assertFalse (i )
369+ # Arrival of the signal shouldn't have changed anything.
370+ i = self .readpipe_interrupted ()
371+ self .assertFalse (i )
372+
373+
301374
302375class ItimerTest (unittest .TestCase ):
303376 def setUp (self ):
0 commit comments