121 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			121 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			Python
		
	
	
	
 | 
						|
""" boxing - wrapping process with another process so we can run
 | 
						|
a process inside and see if it crashes
 | 
						|
"""
 | 
						|
 | 
						|
import py
 | 
						|
import os
 | 
						|
import sys
 | 
						|
import marshal
 | 
						|
from py.__.test import config as pytestconfig
 | 
						|
 | 
						|
PYTESTSTDOUT = "pyteststdout"
 | 
						|
PYTESTSTDERR = "pyteststderr"
 | 
						|
PYTESTRETVAL = "pytestretval"
 | 
						|
 | 
						|
import tempfile
 | 
						|
import itertools
 | 
						|
from StringIO import StringIO
 | 
						|
 | 
						|
counter = itertools.count().next
 | 
						|
 | 
						|
class FileBox(object):
 | 
						|
    def __init__(self, fun, args=None, kwargs=None, config=None):
 | 
						|
        if args is None:
 | 
						|
            args = []
 | 
						|
        if kwargs is None:
 | 
						|
            kwargs = {}
 | 
						|
        self.fun = fun
 | 
						|
        self.config = config
 | 
						|
        assert self.config
 | 
						|
        self.args = args
 | 
						|
        self.kwargs = kwargs
 | 
						|
    
 | 
						|
    def run(self, continuation=False):
 | 
						|
        # XXX we should not use py.test.ensuretemp here
 | 
						|
        count = counter()
 | 
						|
        tempdir = py.test.ensuretemp("box%d" % count)
 | 
						|
        self.tempdir = tempdir
 | 
						|
        self.PYTESTRETVAL = tempdir.join('retval')
 | 
						|
        self.PYTESTSTDOUT = tempdir.join('stdout')
 | 
						|
        self.PYTESTSTDERR = tempdir.join('stderr')
 | 
						|
 | 
						|
        nice_level = self.config.getvalue('dist_nicelevel')
 | 
						|
        pid = os.fork()
 | 
						|
        if pid:
 | 
						|
            if not continuation:
 | 
						|
                self.parent(pid)
 | 
						|
            else:
 | 
						|
                return self.parent, pid
 | 
						|
        else:
 | 
						|
            try:
 | 
						|
                outcome = self.children(nice_level)
 | 
						|
            except:
 | 
						|
                excinfo = py.code.ExceptionInfo()
 | 
						|
                x = open("/tmp/traceback", "w")
 | 
						|
                print >>x, "Internal box error"
 | 
						|
                for i in excinfo.traceback:
 | 
						|
                    print >>x, str(i)[2:-1]
 | 
						|
                print >>x, excinfo
 | 
						|
                x.close()
 | 
						|
                os._exit(1)
 | 
						|
            os.close(1)
 | 
						|
            os.close(2)
 | 
						|
            os._exit(0)
 | 
						|
        return pid
 | 
						|
    
 | 
						|
    def children(self, nice_level):
 | 
						|
        # right now we need to call a function, but first we need to
 | 
						|
        # map all IO that might happen
 | 
						|
        # make sure sys.stdout points to file descriptor one
 | 
						|
        sys.stdout = stdout = self.PYTESTSTDOUT.open('w')
 | 
						|
        sys.stdout.flush()
 | 
						|
        fdstdout = stdout.fileno()
 | 
						|
        if fdstdout != 1:
 | 
						|
            os.dup2(fdstdout, 1)
 | 
						|
        sys.stderr = stderr = self.PYTESTSTDERR.open('w')
 | 
						|
        fdstderr = stderr.fileno()
 | 
						|
        if fdstderr != 2:
 | 
						|
            os.dup2(fdstderr, 2)
 | 
						|
        retvalf = self.PYTESTRETVAL.open("w")
 | 
						|
        try:
 | 
						|
            if nice_level:
 | 
						|
                os.nice(nice_level)
 | 
						|
            # with fork() we have duplicated py.test's basetemp
 | 
						|
            # directory so we want to set it manually here. 
 | 
						|
            # this may be expensive for some test setups, 
 | 
						|
            # but that is what you get with boxing. 
 | 
						|
            # XXX but we are called in more than strict boxing
 | 
						|
            # mode ("AsyncExecutor") so we can't do the following without
 | 
						|
            # inflicting on --dist speed, hum: 
 | 
						|
            # pytestconfig.basetemp = self.tempdir.join("childbasetemp")
 | 
						|
            retval = self.fun(*self.args, **self.kwargs)
 | 
						|
            retvalf.write(marshal.dumps(retval))
 | 
						|
        finally:
 | 
						|
            stdout.close()
 | 
						|
            stderr.close()
 | 
						|
            retvalf.close()
 | 
						|
        os._exit(0)
 | 
						|
    
 | 
						|
    def parent(self, pid, waiter=os.waitpid):
 | 
						|
        pid, exitstat = waiter(pid, 0)
 | 
						|
        self.signal = exitstat & 0x7f
 | 
						|
        self.exitstat = exitstat & 0xff00
 | 
						|
 | 
						|
        
 | 
						|
        if not exitstat:
 | 
						|
            retval = self.PYTESTRETVAL.open()
 | 
						|
            try:
 | 
						|
                retval_data = retval.read()
 | 
						|
            finally:
 | 
						|
                retval.close()
 | 
						|
            self.retval = marshal.loads(retval_data)
 | 
						|
        else:
 | 
						|
            self.retval = None
 | 
						|
        
 | 
						|
        self.stdoutrepr = self.PYTESTSTDOUT.read()
 | 
						|
        self.stderrrepr = self.PYTESTSTDERR.read()
 | 
						|
        return self.stdoutrepr, self.stderrrepr
 | 
						|
 | 
						|
Box = FileBox
 |