251 lines
		
	
	
		
			9.6 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			251 lines
		
	
	
		
			9.6 KiB
		
	
	
	
		
			Python
		
	
	
	
| 
 | |
| """ Remote session base class
 | |
| """
 | |
| 
 | |
| import os
 | |
| import py
 | |
| import sys
 | |
| import re
 | |
| import time
 | |
| 
 | |
| from py.__.test.rsession import repevent
 | |
| from py.__.test.rsession.master import MasterNode, dispatch_loop, itemgen
 | |
| from py.__.test.rsession.hostmanage import HostInfo, HostManager
 | |
| from py.__.test.rsession.local import local_loop, plain_runner, apigen_runner,\
 | |
|     box_runner
 | |
| from py.__.test.rsession.reporter import LocalReporter, RemoteReporter
 | |
| from py.__.test.session import Session
 | |
| from py.__.test.outcome import Skipped, Failed
 | |
| 
 | |
| class AbstractSession(Session): 
 | |
|     """
 | |
|         An abstract session executes collectors/items through a runner. 
 | |
| 
 | |
|     """
 | |
|     def fixoptions(self):
 | |
|         option = self.config.option 
 | |
|         if option.runbrowser and not option.startserver:
 | |
|             #print "--runbrowser implies --startserver"
 | |
|             option.startserver = True
 | |
|         if self.config.getvalue("dist_boxed"):
 | |
|             option.boxed = True
 | |
|         super(AbstractSession, self).fixoptions()
 | |
| 
 | |
|     def init_reporter(self, reporter, hosts, reporter_class, arg=""):
 | |
|         """ This initialises so called `reporter` class, which will
 | |
|         handle all event presenting to user. Does not get called
 | |
|         if main received custom reporter
 | |
|         """
 | |
|         startserverflag = self.config.option.startserver
 | |
|         restflag = self.config.option.restreport
 | |
|         
 | |
|         if startserverflag and reporter is None:
 | |
|             from py.__.test.rsession.web import start_server, exported_methods
 | |
|             if self.config.option.runbrowser:
 | |
|                 from socket import INADDR_ANY
 | |
|                 port = INADDR_ANY   # pick a random port when starting browser
 | |
|             else:
 | |
|                 port = 8000         # stick to a fixed port otherwise
 | |
|             
 | |
|             reporter = exported_methods.report
 | |
|             httpd = start_server(server_address = ('', port))
 | |
|             port = httpd.server_port
 | |
|             if self.config.option.runbrowser:
 | |
|                 import webbrowser, thread
 | |
|                 # webbrowser.open() may block until the browser finishes or not
 | |
|                 url = "http://localhost:%d" % (port,)
 | |
|                 thread.start_new_thread(webbrowser.open, (url,))
 | |
|         elif reporter is None: 
 | |
|             if restflag:
 | |
|                 from py.__.test.rsession.rest import RestReporter
 | |
|                 reporter_class = RestReporter
 | |
|             if arg:
 | |
|                 reporter_instance = reporter_class(self.config, hosts)
 | |
|             else:
 | |
|                 reporter_instance = reporter_class(self.config, hosts)
 | |
|             reporter = reporter_instance.report
 | |
|         else:
 | |
|             startserverflag = False
 | |
|         
 | |
|         return reporter, startserverflag
 | |
|     
 | |
|     def reporterror(reporter, data):
 | |
|         excinfo, item = data
 | |
|         if excinfo is None:
 | |
|             reporter(repevent.ItemStart(item))
 | |
|         elif excinfo.type is Skipped:
 | |
|             reporter(repevent.SkippedTryiter(excinfo, item))
 | |
|         else:
 | |
|             reporter(repevent.FailedTryiter(excinfo, item))
 | |
|     reporterror = staticmethod(reporterror)
 | |
| 
 | |
|     def kill_server(self, startserverflag):
 | |
|         """ Kill web server
 | |
|         """
 | |
|         if startserverflag:
 | |
|             from py.__.test.rsession.web import kill_server
 | |
|             kill_server()
 | |
| 
 | |
|     def wrap_reporter(self, reporter):
 | |
|         """ We wrap reporter around, which makes it possible to us to track
 | |
|         existance of failures
 | |
|         """
 | |
|         self.was_failure = False
 | |
|         def new_reporter(event):
 | |
|             if isinstance(event, repevent.ReceivedItemOutcome) and \
 | |
|                    not event.outcome.passed and \
 | |
|                    not event.outcome.skipped:
 | |
|                 self.was_failure = True
 | |
|             return reporter(event)
 | |
|         checkfun = lambda : self.config.option.exitfirst and \
 | |
|                             self.was_failure
 | |
|         # for tests
 | |
|         self.checkfun = checkfun
 | |
|         return new_reporter, checkfun
 | |
| 
 | |
| class RSession(AbstractSession):
 | |
|     """ Remote version of session
 | |
|     """
 | |
|     def fixoptions(self):
 | |
|         super(RSession, self).fixoptions()
 | |
|         option = self.config.option 
 | |
|         if option.nocapture:
 | |
|             print "Cannot use nocapture with distributed testing"
 | |
|             sys.exit(1)
 | |
|         config = self.config
 | |
|         try:
 | |
|             config.getvalue('dist_hosts')
 | |
|         except KeyError:
 | |
|             print "For running ad-hoc distributed tests you need to specify"
 | |
|             print "dist_hosts in a local conftest.py file, for example:"
 | |
|             print "for example:"
 | |
|             print
 | |
|             print "  dist_hosts = ['localhost'] * 4 # for 3 processors"
 | |
|             print "  dist_hosts = ['you@remote.com', '...'] # for testing on ssh accounts"
 | |
|             print "   # with your remote ssh accounts"
 | |
|             print 
 | |
|             print "see also: http://codespeak.net/py/current/doc/test.html#automated-distributed-testing"
 | |
|             raise SystemExit
 | |
|     
 | |
|     def main(self, reporter=None):
 | |
|         """ main loop for running tests. """
 | |
|         args = self.config.args
 | |
| 
 | |
|         hm = HostManager(self.config)
 | |
|         reporter, startserverflag = self.init_reporter(reporter,
 | |
|             hm.hosts, RemoteReporter)
 | |
|         reporter, checkfun = self.wrap_reporter(reporter)
 | |
| 
 | |
|         reporter(repevent.TestStarted(hm.hosts, self.config.topdir,
 | |
|                                       hm.roots))
 | |
| 
 | |
|         try:
 | |
|             nodes = hm.setup_hosts(reporter)
 | |
|             reporter(repevent.RsyncFinished())
 | |
|             try:
 | |
|                 self.dispatch_tests(nodes, reporter, checkfun)
 | |
|             except (KeyboardInterrupt, SystemExit):
 | |
|                 print >>sys.stderr, "C-c pressed waiting for gateways to teardown..."
 | |
|                 channels = [node.channel for node in nodes]
 | |
|                 hm.kill_channels(channels)
 | |
|                 hm.teardown_gateways(reporter, channels)
 | |
|                 print >>sys.stderr, "... Done"
 | |
|                 raise
 | |
| 
 | |
|             channels = [node.channel for node in nodes]
 | |
|             hm.teardown_hosts(reporter, channels, nodes, 
 | |
|                               exitfirst=self.config.option.exitfirst)
 | |
|             reporter(repevent.Nodes(nodes))
 | |
|             retval = reporter(repevent.TestFinished())
 | |
|             self.kill_server(startserverflag)
 | |
|             return retval
 | |
|         except (KeyboardInterrupt, SystemExit):
 | |
|             reporter(repevent.InterruptedExecution())
 | |
|             self.kill_server(startserverflag)
 | |
|             raise
 | |
|         except:
 | |
|             reporter(repevent.CrashedExecution())
 | |
|             self.kill_server(startserverflag)
 | |
|             raise
 | |
| 
 | |
|     def dispatch_tests(self, nodes, reporter, checkfun):
 | |
|         colitems = self.config.getcolitems()
 | |
|         keyword = self.config.option.keyword
 | |
|         itemgenerator = itemgen(colitems, reporter, keyword, self.reporterror)
 | |
|         
 | |
|         all_tests = dispatch_loop(nodes, itemgenerator, checkfun)
 | |
| 
 | |
| class LSession(AbstractSession):
 | |
|     """ Local version of session
 | |
|     """
 | |
|     def main(self, reporter=None, runner=None):
 | |
|         # check out if used options makes any sense
 | |
|         args = self.config.args  
 | |
|        
 | |
|         hm = HostManager(self.config, hosts=[HostInfo('localhost')])
 | |
|         hosts = hm.hosts
 | |
|         if not self.config.option.nomagic:
 | |
|             py.magic.invoke(assertion=1)
 | |
| 
 | |
|         reporter, startserverflag = self.init_reporter(reporter, 
 | |
|             hosts, LocalReporter, args[0])
 | |
|         reporter, checkfun = self.wrap_reporter(reporter)
 | |
|         
 | |
|         reporter(repevent.TestStarted(hosts, self.config.topdir, []))
 | |
|         colitems = self.config.getcolitems()
 | |
|         reporter(repevent.RsyncFinished())
 | |
| 
 | |
|         if runner is None:
 | |
|             runner = self.init_runner()
 | |
| 
 | |
|         keyword = self.config.option.keyword
 | |
| 
 | |
|         itemgenerator = itemgen(colitems, reporter, keyword, self.reporterror)
 | |
|         local_loop(self, reporter, itemgenerator, checkfun, self.config, runner=runner)
 | |
|         
 | |
|         retval = reporter(repevent.TestFinished())
 | |
|         self.kill_server(startserverflag)
 | |
| 
 | |
|         if not self.config.option.nomagic:
 | |
|             py.magic.revoke(assertion=1)
 | |
| 
 | |
|         self.write_docs()
 | |
|         return retval
 | |
| 
 | |
|     def write_docs(self):
 | |
|         if self.config.option.apigen:
 | |
|             from py.__.apigen.tracer.docstorage import DocStorageAccessor
 | |
|             apigen = py.path.local(self.config.option.apigen).pyimport()
 | |
|             if not hasattr(apigen, 'build'):
 | |
|                 raise NotImplementedError("%s does not contain 'build' "
 | |
|                                           "function" %(apigen,))
 | |
|             print >>sys.stderr, 'building documentation'
 | |
|             capture = py.io.StdCaptureFD()
 | |
|             try:
 | |
|                 pkgdir = py.path.local(self.config.args[0]).pypkgpath()
 | |
|                 apigen.build(pkgdir,
 | |
|                              DocStorageAccessor(self.docstorage),
 | |
|                              capture)
 | |
|             finally:
 | |
|                 capture.reset()
 | |
|             print >>sys.stderr, '\ndone'
 | |
| 
 | |
|     def init_runner(self):
 | |
|         if self.config.option.apigen:
 | |
|             from py.__.apigen.tracer.tracer import Tracer, DocStorage
 | |
|             pkgdir = py.path.local(self.config.args[0]).pypkgpath()
 | |
|             apigen = py.path.local(self.config.option.apigen).pyimport()
 | |
|             if not hasattr(apigen, 'get_documentable_items'):
 | |
|                 raise NotImplementedError("Provided script does not seem "
 | |
|                                           "to contain get_documentable_items")
 | |
|             pkgname, items = apigen.get_documentable_items(pkgdir)
 | |
|             self.docstorage = DocStorage().from_dict(items,
 | |
|                                                      module_name=pkgname)
 | |
|             self.tracer = Tracer(self.docstorage)
 | |
|             return apigen_runner
 | |
|         elif self.config.option.boxed:
 | |
|             return box_runner
 | |
|         else:
 | |
|             return plain_runner
 | |
| 
 |