209 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			209 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			Python
		
	
	
	
import py
 | 
						|
import sys, os
 | 
						|
from py.__.test.dsession.masterslave import MasterNode
 | 
						|
from py.__.test import event
 | 
						|
 | 
						|
class Host(object):
 | 
						|
    """ Host location representation for distributed testing. """ 
 | 
						|
    _hostname2list = {}
 | 
						|
    
 | 
						|
    def __init__(self, spec, addrel="", python=None):
 | 
						|
        parts = spec.split(':', 1)
 | 
						|
        self.hostname = parts.pop(0)
 | 
						|
        self.relpath = parts and parts.pop(0) or ""
 | 
						|
        if self.hostname == "localhost" and not self.relpath:
 | 
						|
            self.inplacelocal = True
 | 
						|
            addrel = "" # inplace localhosts cannot have additions
 | 
						|
        else:
 | 
						|
            self.inplacelocal = False 
 | 
						|
            if not self.relpath:
 | 
						|
                self.relpath = "pytestcache-%s" % self.hostname
 | 
						|
        if addrel:
 | 
						|
            self.relpath += "/" + addrel # XXX too os-dependent
 | 
						|
        assert not parts
 | 
						|
        assert self.inplacelocal or self.relpath
 | 
						|
        self.hostid = self._getuniqueid(self.hostname) 
 | 
						|
        self.python = python 
 | 
						|
 | 
						|
    def __getstate__(self):
 | 
						|
        return (self.hostname, self.relpath, self.hostid)
 | 
						|
       
 | 
						|
    def __setstate__(self, repr):
 | 
						|
        self.hostname, self.relpath, self.hostid = repr
 | 
						|
 | 
						|
    def _getuniqueid(self, hostname):
 | 
						|
        l = self._hostname2list.setdefault(hostname, [])
 | 
						|
        hostid = hostname + "[%d]" % len(l)
 | 
						|
        l.append(hostid)
 | 
						|
        return hostid
 | 
						|
 | 
						|
    def initgateway(self):
 | 
						|
        python = self.python or "python"
 | 
						|
        if self.hostname == "localhost":
 | 
						|
            self.gw = py.execnet.PopenGateway(python=python)
 | 
						|
        else:
 | 
						|
            self.gw = py.execnet.SshGateway(self.hostname, 
 | 
						|
                                            remotepython=python)
 | 
						|
        if self.inplacelocal:
 | 
						|
            self.gw.remote_exec(py.code.Source(
 | 
						|
                sethomedir, "sethomedir()"
 | 
						|
            )).waitclose()
 | 
						|
            self.gw_remotepath = None
 | 
						|
        else:
 | 
						|
            assert self.relpath
 | 
						|
            channel = self.gw.remote_exec(py.code.Source(
 | 
						|
                gethomedir,
 | 
						|
                sethomedir, "sethomedir()", 
 | 
						|
                getpath_relto_home, """
 | 
						|
                channel.send(getpath_relto_home(%r))
 | 
						|
            """ % self.relpath,
 | 
						|
            ))
 | 
						|
            self.gw_remotepath = channel.receive()
 | 
						|
 | 
						|
    def __str__(self):
 | 
						|
        return "<Host id=%s %s:%s>" % (self.hostid, self.hostname, self.relpath)
 | 
						|
    __repr__ = __str__
 | 
						|
 | 
						|
    def __hash__(self):
 | 
						|
        return hash(self.hostid)
 | 
						|
 | 
						|
    def __eq__(self, other):
 | 
						|
        return self.hostid == other.hostid
 | 
						|
 | 
						|
    def __ne__(self, other):
 | 
						|
        return not self.hostid == other.hostid
 | 
						|
 | 
						|
class HostRSync(py.execnet.RSync):
 | 
						|
    """ RSyncer that filters out common files 
 | 
						|
    """
 | 
						|
    def __init__(self, sourcedir, *args, **kwargs):
 | 
						|
        self._synced = {}
 | 
						|
        ignores= None
 | 
						|
        if 'ignores' in kwargs:
 | 
						|
            ignores = kwargs.pop('ignores')
 | 
						|
        self._ignores = ignores or []
 | 
						|
        super(HostRSync, self).__init__(sourcedir=sourcedir, **kwargs)
 | 
						|
 | 
						|
    def filter(self, path):
 | 
						|
        path = py.path.local(path)
 | 
						|
        if not path.ext in ('.pyc', '.pyo'):
 | 
						|
            if not path.basename.endswith('~'): 
 | 
						|
                if path.check(dotfile=0):
 | 
						|
                    for x in self._ignores:
 | 
						|
                        if path == x:
 | 
						|
                            break
 | 
						|
                    else:
 | 
						|
                        return True
 | 
						|
 | 
						|
    def add_target_host(self, host, destrelpath="", notify=None):
 | 
						|
        remotepath = host.gw_remotepath 
 | 
						|
        key = host.hostname, host.relpath
 | 
						|
        if host.inplacelocal: 
 | 
						|
            remotepath = self._sourcedir
 | 
						|
            self._synced[key] = True
 | 
						|
        elif destrelpath:
 | 
						|
            remotepath = os.path.join(remotepath, destrelpath)
 | 
						|
        synced = key in self._synced 
 | 
						|
        if notify: 
 | 
						|
            notify(
 | 
						|
                event.HostRSyncing(host, py.path.local(self._sourcedir), 
 | 
						|
                                      remotepath, synced))
 | 
						|
        def hostrsynced(host=host):
 | 
						|
            if notify: 
 | 
						|
                notify(
 | 
						|
                    event.HostRSyncRootReady(host, self._sourcedir))
 | 
						|
        if key in self._synced:
 | 
						|
            hostrsynced()
 | 
						|
            return
 | 
						|
        self._synced[key] = True
 | 
						|
        super(HostRSync, self).add_target(host.gw, remotepath, 
 | 
						|
                                          finishedcallback=hostrsynced,
 | 
						|
                                          delete=True,
 | 
						|
                                          )
 | 
						|
        return remotepath 
 | 
						|
 | 
						|
def gethosts(config, addrel):
 | 
						|
    if config.option.numprocesses:
 | 
						|
        hosts = ['localhost'] * config.option.numprocesses
 | 
						|
    else:
 | 
						|
        hosts = config.getvalue("dist_hosts")
 | 
						|
    python = config.option.executable or "python"
 | 
						|
    hosts = [Host(x, addrel, python=python) for x in hosts]
 | 
						|
    return hosts
 | 
						|
    
 | 
						|
class HostManager(object):
 | 
						|
    def __init__(self, session, hosts=None):
 | 
						|
        self.session = session 
 | 
						|
        roots = self.session.config.getvalue_pathlist("dist_rsync_roots")
 | 
						|
        addrel = ""
 | 
						|
        if roots is None:
 | 
						|
            roots = [self.session.config.topdir]
 | 
						|
            addrel = self.session.config.topdir.basename 
 | 
						|
        self._addrel = addrel
 | 
						|
        self.roots = roots
 | 
						|
        if hosts is None:
 | 
						|
            hosts = gethosts(self.session.config, addrel)
 | 
						|
        self.hosts = hosts
 | 
						|
 | 
						|
    def prepare_gateways(self):
 | 
						|
        for host in self.hosts:
 | 
						|
            host.initgateway()
 | 
						|
            self.session.bus.notify(event.HostGatewayReady(host, self.roots))
 | 
						|
 | 
						|
    def init_rsync(self):
 | 
						|
        self.prepare_gateways()
 | 
						|
        # send each rsync root
 | 
						|
        ignores = self.session.config.getvalue_pathlist("dist_rsync_ignore")
 | 
						|
        for root in self.roots:
 | 
						|
            rsync = HostRSync(root, ignores=ignores, 
 | 
						|
                              verbose=self.session.config.option.verbose)
 | 
						|
            if self._addrel: 
 | 
						|
                destrelpath = ""
 | 
						|
            else:
 | 
						|
                destrelpath = root.basename
 | 
						|
            for host in self.hosts:
 | 
						|
                rsync.add_target_host(host, destrelpath)
 | 
						|
            rsync.send(raises=False)
 | 
						|
        self.session.bus.notify(event.RsyncFinished())
 | 
						|
 | 
						|
    def setup_hosts(self, notify=None):
 | 
						|
        if notify is None:
 | 
						|
            notify = self.session.bus.notify
 | 
						|
        self.init_rsync()
 | 
						|
        for host in self.hosts:
 | 
						|
            host.node = MasterNode(host, 
 | 
						|
                                   self.session.config, 
 | 
						|
                                   notify)
 | 
						|
 | 
						|
#
 | 
						|
# helpers 
 | 
						|
#
 | 
						|
def gethomedir():
 | 
						|
    import os
 | 
						|
    homedir = os.environ.get('HOME', '')
 | 
						|
    if not homedir:
 | 
						|
        homedir = os.environ.get('HOMEPATH', '.')
 | 
						|
    return os.path.abspath(homedir)
 | 
						|
 | 
						|
def getpath_relto_home(targetpath):
 | 
						|
    import os
 | 
						|
    if not os.path.isabs(targetpath):
 | 
						|
        homedir = gethomedir()
 | 
						|
        targetpath = os.path.join(homedir, targetpath)
 | 
						|
    return os.path.normpath(targetpath)
 | 
						|
 | 
						|
def sethomedir():
 | 
						|
    import os
 | 
						|
    homedir = os.environ.get('HOME', '')
 | 
						|
    if not homedir:
 | 
						|
        homedir = os.environ.get('HOMEPATH', '.')
 | 
						|
    os.chdir(homedir)
 | 
						|
 | 
						|
def makehostup(host=None):
 | 
						|
    if host is None:
 | 
						|
        host = Host("localhost")
 | 
						|
    platinfo = {}
 | 
						|
    for name in 'platform', 'executable', 'version_info':
 | 
						|
        platinfo["sys."+name] = getattr(sys, name)
 | 
						|
    return event.HostUp(host, platinfo)
 |